二叉树与赫夫曼图片压缩实践--数据结构--C语言、c++语言版--可运行
数据结构C++,实现哈夫曼树压缩编码程序,实现图片的压缩
·
实验目标
1
、掌握树的存储结构。
2
、掌握二叉树的三种遍历方法。
3
、掌握
Huffman
树、
Huffman
编码等知识和应用。
4
、使用
C++
、文件操作和
Huffman
算法实现“图片压缩程序”专题编程。
实验任务
压缩软件是利用特定算法来压缩数据的工具,压缩后生成的文件称为压缩包
(archive)
。
如果想使用其中的数据,就得用压缩软件对数据进行解压。利用压缩软件对文件中重复的数
据进行压缩,可以减小文件中的字节总数,使文件能够通过互联网连接实现更快传输,此外
还可以减少文件的磁盘占用空间。常用的压缩软件有
rar
、
zip
等。
压缩可以分为无损压缩与有损压缩两种。无损压缩后的文件,经过解压能够完全恢复原
始数据;有损压缩的文件则无法完全恢复。
rar
、
zip
等格式都是无损压缩格式。音乐文件格
式
mp3
、图片文件格式
jpg
都是有损压缩格式。
计算机文件是由一个个字节组成的,
1
个字节有
0~255
共
256
种可能的取值,每个字节
的编码长度都是
8
位。由于文件中的字节总是会重复出现,可以对不同的字节设计长度不等
的编码,让出现次数较多的字节,采用尽可能短的编码,那么文件编码的总长便可减少。
统计文件中
256
种不同的字节重复的次数,以每种字节重复的次数作为权值
(weight)
,
构造一棵有
256
个叶子节点的二叉树。若带权路径长度达到最小,称这样的二叉树为最优二
叉树,即
Huffman
树
(Huffman tree)
。
Huffman
树从根到每个叶子都有一条路径。对路径上的各分支,约定指向左子树根的分
支编码为
“0”
,指向右子树根的分支编码为
“1”
。从根到每个叶子相应路径上的
“0”
和
“1”
组成
的序列,就是这个叶子节点的编码,称为
Huffman
编码。
废话少说,直接上代码
数据流头文件:BitIO.h
#pragma once
#pragma warning(disable:4996)
#ifndef HUFFMANCOMPRESSCPRO1_BITIO_H
#define HUFFMANCOMPRESSCPRO1_BITIO_H
#include <iostream>
#define BITBUFFSIZE 1024
#define SHIFT 3
struct BIT {
char b[BITBUFFSIZE];//位数组 bit数组
int p; //指示数组填到哪一位的下一位
};
//向位数组栈顶推入一位
bool pushBit(BIT* buffer, const bool istrue);
//从文件加载多位
int fPushBit(BIT* buffer, FILE* fp);
//修改position位置的一位
int changeBit(BIT* buffer, const int istrue, const int position);
//读取一位
int readBit(BIT* buffer, const int position);
//栈顶弹出一位
int popBit(BIT* buffer);
#endif
压缩程序头文件:Compress.h
#pragma once
#pragma warning(disable:4996)
#ifndef HUFFMANCOMPRESSCPRO1_COMPRESS_H
#define HUFFMANCOMPRESSCPRO1_COMPRESS_H
void Compress(char* Filename);
#endif
哈夫曼树头文件:Huffman.h
#pragma once
#pragma warning(disable:4996)
#ifndef HUFFMANCOMPRESSCPRO1_HUFFMANTREE_H
#define HUFFMANCOMPRESSCPRO1_HUFFMANTREE_H
#include <string>
#include <iostream>
#include "BitIO.h"
#define SIZE 256
#define NODES 2*SIZE - 1
//取出index位,若取出的index位为0,则GET_BYTE值为假,否则为真
#define GET_BYTE(vByte, index) (((vByte) & (1 << ((index) ^ 7))) != 0)
//把index位设置为‘1’
#define SET_BYTE(vbyte, index) ((vbyte) |= (1 << ((index) ^ 7)))
//把index位设置为‘0’
#define CLR_BYTE(vbyte, index) ((vbyte) &= (~(1 << ((index) ^ 7))))
//n个叶子结点的哈夫曼树共有2n-1个结点
//一个字节8位 读取文件的char可以从0-255 用weight[]数组记录每个char出现的频率
//HuffmanTree 保存在HTNode[2n-1]的数组中
struct Node {
bool isLeaf();
int ch = 0;
int weight = 0;
int lChild = 0, rChild = 0, parent = 0;
};
typedef Node HTNode, * HuffTree;
struct HEAD {
char type[4];
int length;
int weight[256];
};
void expand();
void select(HuffTree HT, int end, int* s1, int* s2);
void buildCode(std::string st[], Node x, std::string s, HuffTree HT);
std::string* buildCode(Node root, HuffTree HT);
void testBuildCode(std::string* st);
void InitTrie(int* w, HuffTree& HT);
void buildTrie(int* w, HuffTree& HT);
void testBuildTrie(int* w, HuffTree HT);
int InitHead(const char* Filename, HEAD& sHead);
unsigned char Str2Byte(char* BinStr);
void writeTrie(Node x, FILE* fpo, HuffTree& HT);
void encode(FILE* fpi, FILE* fpo, std::string* st);
void writeHead(FILE* fpo, HEAD& head);
int Extract();
#endif
三个头文件结束
头文件解释:
#pragma once
防止出现重复头文件
#pragma warning(disable:4996)
解决可能会出现的4996问题
错误 C4996 'scanf': This function or variable may be unsafe. Consider using scanf_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.
数据流源文件:BitIO.cpp
#include "BitIO.h"
bool pushBit(BIT* buffer, const bool istrue) {
if (buffer->p >= BITBUFFSIZE * 8)
return EOF;
else if (istrue)
buffer->b[buffer->p >> SHIFT] |= 128u >> buffer->p % 8; //p所指位置填1
else
buffer->b[buffer->p >> SHIFT] &= ~(128u >> buffer->p % 8); //p所指位置填0
buffer->p++;
return istrue;
}
int fPushBit(BIT* buffer, FILE* fp) {
memset(buffer, 0, sizeof(BIT));
if (buffer->p = fread(buffer->b, sizeof(char), BITBUFFSIZE, fp) && feof(fp)) {
buffer->p = (buffer->p - 2) * 8 + buffer->b[buffer->p - 1] + 1;
}
else
buffer->p *= 8;
return buffer->p;
}
int changeBit(BIT* buffer, const int istrue, const int position) {
if (position >= buffer->p)
return EOF;
else if (istrue)
buffer->b[position >> SHIFT] |= 128u >> position % 8;
else
buffer->b[position >> SHIFT] &= ~(128u >> position % 8);
return istrue;
}
int readBit(BIT* buffer, const int position) {
if (position >= buffer->p)
return EOF;
else
return buffer->b[position >> SHIFT] & (128u >> position % 8);
}
int popBit(BIT* buffer) {
if (buffer->p >= BITBUFFSIZE || buffer->p < 0)
return EOF;
buffer->p--;
return buffer->b[(buffer->p + 1) >> SHIFT] & (128u >> (buffer->p + 1) % 8);
}
压缩程序源文件:Compress.cpp
#include "Compress.h"
#include "Huffman.h"
void Compress(char* Filename) {
int weight[SIZE] = { 0 };
FILE* fin = fopen(Filename, "rb");
if (!fin) {
std::cerr << "File opening failed" << std::endl;
exit(EXIT_FAILURE);
}
int ch;
while ((ch = fgetc(fin)) != EOF) {
weight[ch]++;
}
fclose(fin);
HuffTree HT;
InitTrie(weight, HT);
buildTrie(weight, HT);
testBuildTrie(weight, HT);
std::string st[SIZE];
buildCode(st, HT[NODES - 1], "", HT);
std::cout << "HuffmanCode: " << std::endl;
std::cout << "Bytes \t Codes" << std::endl;
for (int i = 0; i < SIZE; i++) {
printf("0x%02X :", i);
printf("%s\n", st[i].c_str());
}
//testBuildCode(str);
// std::cout<<"HuffmanCode: "<<std::endl;
// std::cout<<"Bytes \t Codes"<<std::endl;
// for(int i= 0; i < SIZE; i++){
// printf("0x%02X :", i);
// printf("%s\n",str[i].c_str());
// }
HEAD head;
InitHead(Filename, head);
char filename[256] = { '\0' };
strcpy(filename, Filename);
strcat(filename, ".huf");
FILE* out = fopen(filename, "wb");
writeHead(out, head);
fin = fopen(Filename, "rb");
encode(fin, out, st);
fclose(fin);
fclose(out);
Extract();
}
哈夫曼树源文件:Huffman.cpp
#include "Huffman.h"
//HT指向HuffmanTree end为保存树数组所需搜寻的末尾,s1,s2分别指向最小和次小结点在树数组中下标
void select(HuffTree HT, int end, int* s1, int* s2) {
int min1, min2;
int i = 0;
while (HT[i].parent != 0 && i <= end) {
//找到第一个无双亲结点
i++;
}
min1 = HT[i].weight;
*s1 = i;
i++;
while (HT[i].parent != 0 && i <= end) {
//第二个无双亲结点
i++;
}
if (HT[i].weight < min1) {
min2 = min1;
min1 = HT[i].weight;
*s2 = *s1;
*s1 = i;
}
else {
min2 = HT[i].weight;
*s2 = i;
}
for (int j = i + 1; j <= end; j++) {
//两结点与后续无双亲结点比较
if (HT[j].parent != 0) {
continue;
}
if (HT[j].weight < min1) {
//新结点小于min1
min2 = min1;
*s2 = *s1;
min1 = HT[j].weight;
*s1 = j;
}
else if (HT[j].weight >= min1 && HT[j].weight < min2) {
//新结点介于min1,min2 之间
min2 = HT[j].weight;
*s2 = j;
}
}
}
void InitTrie(int* w, HuffTree& HT) {
HT = (HuffTree)malloc(sizeof(HTNode) * NODES);
for (int i = 0; i < SIZE; i++) {
//0-255 叶子结点
HT[i].parent = 0;
HT[i].rChild = 0;
HT[i].lChild = 0;
HT[i].weight = w[i];
HT[i].ch = i;
}
for (int i = SIZE; i < NODES; i++) {
//256-510 内部结点
HT[i].parent = 0;
HT[i].lChild = 0;
HT[i].rChild = 0;
HT[i].weight = 0;
HT[i].ch = 0;
}
}
void buildTrie(int* w, HuffTree& HT) {
//每次搜寻两棵频率最小的树合并
int s1, s2;
for (int i = SIZE; i < NODES; i++) {
//从256开始填树结点,一直到把根结点510填满 NODES = 511
select(HT, i - 1, &s1, &s2);
HT[s1].parent = HT[s2].parent = i;
HT[i].lChild = s1;
HT[i].rChild = s2;
HT[i].weight = HT[s1].weight + HT[s2].weight;
}
}
void testBuildTrie(int* w, HuffTree HT) {
std::cout << "字节ASCII " << "频率" << std::endl;
//文件中出现的字节对应的ASCII(十六进制)和 出现的频率
for (int i = 0; i < SIZE; i++) {
printf("0x%02X %d\n", i, w[i]);
}
//哈夫曼树中所有结点
printf("结点下标\t频率\t双亲下标\t左孩子下标\t右孩子下标\n");
for (int i = 0; i < NODES; i++) {
printf("HT[%d]\t%d\t%d \t%d \t\t%d\t%d\n", i, HT[i].weight, HT[i].parent, HT[i].lChild, HT[i].rChild, HT[i].ch);
}
}
bool Node::isLeaf() { return lChild == 0 && rChild == 0; }
void buildCode(std::string st[], Node x, std::string s, HuffTree HT) {
if (x.isLeaf()) {
st[x.ch] = s;
return;
}
buildCode(st, HT[x.lChild], s + '0', HT);
buildCode(st, HT[x.rChild], s + '1', HT);
}
std::string* buildCode(Node root, HuffTree HT) {
std::string st[SIZE];
buildCode(st, root, "", HT);
std::cout << "HuffmanCode: " << std::endl;
std::cout << "Bytes \t Codes" << std::endl;
for (int i = 0; i < SIZE; i++) {
printf("0x%02X :", i);
printf("%s\n", st[i].c_str());
}
return st;
}
void testBuildCode(std::string* st) {
std::cout << "HuffmanCode: " << std::endl;
std::cout << "Bytes \t Codes" << std::endl;
for (int i = 0; i < SIZE; i++) {
printf("0x%02X :", i);
printf("%s\n", (st + i)->c_str());
}
}
int InitHead(const char* Filename, HEAD& sHead) {
//初始化文件头
char* t = (char*)malloc(sizeof(char) * strlen(Filename));
strcpy(t, const_cast<char*>(Filename));
char* token = std::strtok(t, ".");
std::string str[4];
int it = 0;
while (token != nullptr) {
str[it++] = token;
token = strtok(NULL, ".");
}
char t2[8] = { '\0' };
for (int k = 0; k < str[it - 1].size(); k++) {
t2[k] = str[it - 1].at(k);
}
strcpy(sHead.type, t2);
sHead.length = 0;
for (int i = 0; i < 256; i++) {
sHead.weight[i] = 0;
}
//以二进制流打开文件
FILE* in = fopen(Filename, "rb");
int ch;
while ((ch = fgetc(in)) != EOF) {
sHead.weight[ch]++;
sHead.length++;//原文件长度多少字节
}
fclose(in);
in = nullptr;
return 1;
}
//将8位char变成一个char 即把 char数组 中的 char01 变成 一个char 中的 位01
unsigned char Str2Byte(char* BinStr) {
unsigned char b = 0x00;
for (int i = 0; i < 8; i++)
{
b = b << 1;
if (BinStr[i] == '1')
{
b = b | 0x01;
}
}
return b;
}
//可以将编码好的单词查找树写在文件开头,代价是文件变大, 也可以利用文件头(上述HEAD)中的weight数组在解压时重新构建树
//按位写入查找树
void writeTrie(Node x, FILE* fpo, HuffTree& HT) {
char ch;
if (x.isLeaf()) {
bool t = true;
fwrite(&t, sizeof(bool), 1, fpo);
ch = (char)x.ch;
fwrite(&ch, sizeof(char), 1, fpo);
return;
}
bool t = false;
fwrite(&t, sizeof(bool), 1, fpo);
writeTrie(HT[x.lChild], fpo, HT);
writeTrie(HT[x.rChild], fpo, HT);
}
//原文件编码后写入压缩文件
void encode(FILE* fpi, FILE* fpo, std::string* st) {
int ch;
char cd[SIZE] = { 0 };
int pos = 0;
//unsigned char pBuffer[1000] = {0}; //debug
if (!fpo) {
std::cerr << "outFile opening failed" << std::endl;
exit(EXIT_FAILURE);
}
while ((ch = fgetc(fpi)) != EOF) {
std::string code = st[ch];
strcat(cd, code.c_str());
while (strlen(cd) >= 8) {
unsigned char w = Str2Byte(cd);
//pBuffer[pos++] = w;
fwrite(&w, sizeof(char), 1, fpo);//写入文件
for (int i = 0; i < SIZE - 8; i++) {
//cd整体向左偏移8位
cd[i] = cd[i + 8];
}
}
}
//处理可能剩余不足8位
if (strlen(cd) > 0) {
unsigned char w = Str2Byte(cd);
fwrite(&w, sizeof(char), 1, fpo); //最后不足8位的位都为0
}
}
void writeHead(FILE* fpo, HEAD& head) {
fwrite(&head, sizeof(head), 1, fpo);
}
int Extract() {
std::cout << "File to extract: ";
char efile[256];
std::cin >> efile;
FILE* in = fopen(efile, "rb");
if (!in) {
std::cerr << "File opening failed" << std::endl;
return EXIT_FAILURE;
}
HEAD head;
fread(&head, sizeof(HEAD), 1, in);
char file[SIZE] = { '\0' };
char* t = (char*)malloc(sizeof(char) * strlen(efile));
strcpy(t, const_cast<char*>(efile));
char* token = strtok(t, ".");
strcpy(file, token);
strcat(file, "_extracted.");
strcat(file, head.type);
FILE* out = fopen(file, "wb");
fseek(in, 0L, SEEK_END);
long filesize = ftell(in);
HuffTree HT;
InitTrie(head.weight, HT);
buildTrie(head.weight, HT);
std::string st[SIZE];
buildCode(st, HT[NODES - 1], "", HT);
fseek(in, 1032, SEEK_SET);
long curLoc = ftell(in);
unsigned char outValue;
int index = 0;
int root = NODES - 1;
unsigned char ch = fgetc(in);
fseek(out, 0, SEEK_SET);
while (true) {
if (HT[root].lChild == 0 && HT[root].rChild == 0) {
outValue = (char)HT[root].ch;
fwrite(&outValue, sizeof(char), 1, out);
if (curLoc >= filesize) {
break;
}
root = NODES - 1;
}
if (!(ch & (1 << (index ^ 7)))) {
root = HT[root].lChild;
}
else {
root = HT[root].rChild;
}
if (++index >= 8) {
index = 0;
ch = fgetc(in);
curLoc = ftell(in);
}
}
fclose(in);
fclose(out);
return 1;
}
main程序源文件:main.cpp
#include <iostream>
#include "Huffman.h"
#include "Compress.h"
using namespace std;
int main() {
cout << "==== 哈夫曼压缩 ====" << endl;
cout << "请输入你的文件名: ";
char filename[SIZE];
cin >> filename;
Compress(filename);//解压函数也在这里运行了
return 0;
}
程序到此结束
下面是使用以及效果:
示例图片
首先将图片保存到文件目录下,保存名字1.jpg
运行界面
输入文件名: 1.jpg
输出(很长,截取一段)
输出文件在当前目录下:
打开文件查看:
为了验证解压缩的文件是否正确,还需要将文件的大小保存到文件中去,同时也可以将
文件的类型存入文件中,用于判断是否是需要解压的文件,这里默认的是 HUF 类型。
结束
更多推荐
已为社区贡献1条内容
所有评论(0)