霍夫曼编码算法与实现
来源:互联网 发布:打车软件司机版 编辑:程序博客网 时间:2024/06/14 23:26
本实验包含大量结构体的用法,需要先了解struct(自定数据类型),typedef(数据类型 别名),union(共用体)的用法。先参考《21天学通C语言 第六版》的第十一章
一、实验原理
1. Huffman编码算法
(1)将文件以ASCII字符流的形式读入,统计每个符号的发生频率;
(2)将所有文件中出现过的字符按照频率从小到大的顺序排列;
(3)每一次选出最小的两个值,作为二叉树的两个叶子节点,将和作为它们的根节点,这两个叶子节点不再参与比较,新的根节点参与比较;
(4)重复3,直到最后得到和为1的根节点;
(5)将形成的二叉树的左节点标0,右节点标1,把从最上面的根节点到最下面的叶子节点途中遇到的0, 1序列串起来,得到了各个字符的编码表示。
2. Huffman编码的数据结构设计
在程序实现中使用一种叫做二叉树的数据结构实现Huffman编码。
(1)霍夫曼节点结构
typedef struct huffman_node_tag //节点数据类型 { unsigned char isLeaf; //是否为叶节点,1是0否 unsigned long count; //信源文件中出现频数 struct huffman_node_tag *parent; //父节点指针 union { struct //如果不是叶节点,那么它为该节点左右子节点的指针 { struct huffman_node_tag *zero, *one; }; unsigned char symbol; //如果是叶节点,那么它表示某个信源符号,这里用一个字节的8位二进制数表示 }; } huffman_node;
(2)霍夫曼码结构
typedef struct huffman_code_tag //码字数据类型 { unsigned long numbits; //码字的长度(bit) /* 因为unsign char 类型是以 8 bit 来保存数据,所以码字8个8个一组保存在bits[]中, 例: 码字的第1位存于bits[0]的第1位, 码字的第2位存于bits[0]的第的第2位, 码字的第8位存于bits[0]的第的第8位, 码字的第9位存于bits[1]的第的第1位 */ unsigned char *bits; //指向该码比特串的指针 } huffman_code;
3霍夫曼编码流程
该程序包括两个工程,“huff_run”为主工程,其中包括了“huffcode.c”文件
“Huff_code”为库工程,其中包括了“huffman.c”文件
下面按照实验流程来进行分析
3.1 读入待编码的源文件
如下代码属于”huffcode.c”文件
main(int argc, char** argv) { char memory = 0; //memory为1表示对内存数据进行操作,0表示不操作 char compress = 1; //compress为1表示编码,0表示解码 int opt; const char *file_in = NULL, *file_out = NULL; //step1:add by yzhang for huffman statistics const char *file_out_table = NULL; //end by yzhang FILE *in = stdin; FILE *out = stdout; //step1:add by yzhang for huffman statistics FILE * outTable = NULL; //输出的中间数据的表 //end by yzhang while((opt = getopt(argc, argv, "i:o:cdhvmt:")) != -1) //读取命令行参数的选项 { switch(opt) { case 'i': file_in = optarg; //i表示输入文件 break; case 'o': file_out = optarg; //o表示输出文件 break; case 'c': compress = 1; //c表示进行编码 break; case 'd': compress = 0; //d表示进行解码 break; case 'h': usage(stdout); //h表示输出参数用法说明 return 0; case 'v': version(stdout); //v表示输出版本号信息 return 0; case 'm': memory = 1; //m表示对内存数据进行操作 break; // by yzhang for huffman statistics case 't': file_out_table = optarg; //t表示输出的中间数据信息 break; //end by yzhang default: usage(stderr); return 1; } } //如果输入文件给定,那么读取 if(file_in) { in = fopen(file_in, "rb"); if(!in) { fprintf(stderr, "Can't open input file '%s': %s\n", file_in, strerror(errno)); return 1; } } //如果输出文件给定,那么创建 if(file_out) { out = fopen(file_out, "wb"); if(!out) { fprintf(stderr, "Can't open output file '%s': %s\n", file_out, strerror(errno)); return 1; } } //by yzhang for huffman statistics if(file_out_table) { outTable = fopen(file_out_table, "w"); if(!outTable) { fprintf(stderr, "Can't open output file '%s': %s\n", file_out_table, strerror(errno)); return 1; } } //end by yzhang if(memory) //对内存数据进行编码或解码操作 { return compress ? memory_encode_file(in, out) : memory_decode_file(in, out); } //对文件进行编码或解码操作 if(compress) //change by yzhang huffman_encode_file(in, out,outTable);//step1:changed by yzhang from huffman_encode_file(in, out) to huffman_encode_file(in, out,outTable) else huffman_decode_file(in, out); if(in) fclose(in); if(out) fclose(out); if(outTable) fclose(outTable); return 0; }
3.2 huffman.c第一次扫描,统计信源字符发生频率
因为是8比特,共256个信源符号,所以创建一个256个元素的指针数组,用以保存256个信源符号的频率。其下标对应相应字符的ASCII码。需要注意的是,源文件中不一定所有的信源符号都会出现,因此数组中存在空元素,而非空元素为待编码文件中实际出现的信源符号。
#define MAX_SYMBOLS 256 //typedef huffman_node* SymbolFrequencies[MAX_SYMBOLS]; //信源符号数组 //typedef huffman_code* SymbolEncoder[MAX_SYMBOLS]; //编码后的符号数组
/* 第一次扫描,统计信源字符发生频率 */ static unsigned int get_symbol_frequencies(SymbolFrequencies *pSF, FILE *in) { int c; unsigned int total_count = 0; //将总信源符号数初始化为 0 init_frequencies(pSF); //将所有信源符号地址初始化为NULL(0) //第一次扫描文件,统计输入文件中每个信源符号的发生频率 while((c = fgetc(in)) != EOF) { unsigned char uc = c; if(!(*pSF)[uc]) // 如果是一个新符号,则产生该字符的一个新叶节点 (*pSF)[uc] = new_leaf_node(uc); ++(*pSF)[uc]->count; //如果已经是叶节点了,则字符发生频数+1 ++total_count; //总信源符号数 +1 } return total_count; }
3.3 建立Huffman树并计算符号对应的Huffman码字
(1) 将频率从小到大排序并建立Huffman树
(2) 递归遍历Huffman树,对存在的每个字符计算码字
3.4 将Huffman码表写入文件
4.1 读取码表并重建据此Huffman树
如下代码属于”huffman.c”文件
static huffman_node* read_code_table(FILE* in, unsigned int *pDataBytes) { huffman_node *root = new_nonleaf_node(0, NULL, NULL); unsigned int count; if(fread(&count, sizeof(count), 1, in) != 1)// 读取码表中的符号数 { free_huffman_tree(root); return NULL; } count = ntohl(count); //读取此编码表示的数据字节数 if(fread(pDataBytes, sizeof(*pDataBytes), 1, in) != 1) { free_huffman_tree(root); return NULL; } *pDataBytes = ntohl(*pDataBytes); while(count-- > 0) //读取码表,由符号、码长、码字三部分组成 { int c; unsigned int curbit; unsigned char symbol; //符号 unsigned char numbits; //码长 unsigned char numbytes; //字节数 unsigned char *bytes; huffman_node *p = root; if((c = fgetc(in)) == EOF) //一个字节一个字节的读入 { free_huffman_tree(root); return NULL; } symbol = (unsigned char)c; if((c = fgetc(in)) == EOF) { free_huffman_tree(root); return NULL; } numbits = (unsigned char)c; numbytes = (unsigned char)numbytes_from_numbits(numbits); //计算保存一个码长所需要的字节数 bytes = (unsigned char*)malloc(numbytes); // 为读取码字分配相应的空间 if(fread(bytes, 1, numbytes, in) != numbytes) // 读取码字 { free(bytes); free_huffman_tree(root); return NULL; } //根据码表,进行huffman树的重建,由根节点至叶节点 for(curbit = 0; curbit < numbits; ++curbit) { if(get_bit(bytes, curbit)) // 判断当前读取位是否为’1’ { //若当前读取位为'1' if(p->one == NULL) //建立右子节点 { //判断是否是当前码字的最后一位,若是,新建叶结点;若不是,则新建非叶结点。 p->one = curbit == (unsigned char)(numbits - 1) ? new_leaf_node(symbol) : new_nonleaf_node(0, NULL, NULL); p->one->parent = p; //设置右子节点的父节点 } p = p->one; } else //若当前读取位为'0' { if(p->zero == NULL) //建立左子节点 { p->zero = curbit == (unsigned char)(numbits - 1) ? new_leaf_node(symbol) : new_nonleaf_node(0, NULL, NULL); p->zero->parent = p; } p = p->zero; } } free(bytes); } return root; // 返回Huffman树的根结点,才可以进行之后的遍历 }
4.2 读取Huffman码字,并根据huffman树进行解码输出
如下代码属于”huffman.c”文件
“`
int
huffman_decode_file(FILE *in, FILE *out)
{
huffman_node *root, *p;
int c;
unsigned int data_count;
root = read_code_table(in, &data_count); //读取码表 if(!root) return 1; // Huffman树建立失败 // 文件解码 p = root; // data_count >0 :逻辑上仍有数据;(c = fgetc(in)) != EOF):文件中仍有数据 while(data_count > 0 && (c = fgetc(in)) != EOF) { unsigned char byte = (unsigned char)c; //一个字节的码字 unsigned char mask = 1; // mask用于逐位读出码字 while(data_count > 0 && mask) { //走huffman树,若当前字节为0,就走左子树,否则走右子树 p = byte & mask ? p->one : p->zero; mask <<= 1; //mask每个循环进行左移 if(p->isLeaf) //走到叶结点,标明解码完毕 { fputc(p->symbol, out); //输出叶节点中存储的符号 p = root; //转向根节点,进行下一个码字的解码 --data_count; //将没解码的码字数-1 } } } free_huffman_tree(root); // 所有码字均已解码输出,文件解码完毕 return 0;
}
二、实验结果
选择9种不同格式类型的文件,使用Huffman编码器进行压缩得到输出的压缩比特流文件,并对各种不同格式的文件进行压缩效率的分析
分析结果如下:
1. 表格形式表示的实验结果
2. 各样本文件的概率分布图
3. 实验结果的分析
可以从表格和概率分布图看出,huffman编码的平均码长小于且很接近信源熵,信源熵一定小于8bit/sym
当文件分布概率越不均匀,编码效率越高(如yuv文件);当文件分布概率比较均匀时,压缩效果并不理想
- 霍夫曼编码算法与实现
- CRC编码算法研究与实现
- base64编码算法与c语言实现
- base64编码算法与c语言实现
- 【算法设计与分析】8、哈弗曼编码,贪心算法实现
- Huffman霍夫曼压缩编码算法实现分析
- 哈夫曼编码算法实现
- 算法设计与分析--霍夫曼树编码(C++实现)
- 算法设计与分析--霍夫曼树编码(C++实现)
- UFLDL自编码算法详细理解与代码实现
- deep learning 自编码算法详细理解与代码实现
- 霍夫曼编码介绍和霍夫曼编码的接口与实现
- 霍夫曼编码与解码c++实现
- 编码实现Cohen-Sutherland端点编码算法
- Base64编码算法实现-1
- Base64编码算法实现-2
- 哈夫曼编码算法及其实现
- DES 算法 信息论与编码
- 指针
- Oracle游标的使用
- vim常用命令
- ctags使用详解
- Android与JavaWeb服务器交互教程(1)-搭建环境
- 霍夫曼编码算法与实现
- toj-1036-火车进站
- JavaScript动画1-速度动画
- R3获取kernel32地址
- 关于IDE选择的一些思考
- ubuntu下配置cuteFTP
- flume 的source 、channel和sink 多种组合
- Android Task的应用
- mybatis的mapper代理方法开发实现用户的增删改查