哈弗曼编码 

 

实验报告

科目:   数据结构    姓名:   朱凯迪     实验日期:   2010-12-29   

实验名称:            哈弗曼编码             ;

 

 

一、实验目的

1、  熟悉哈夫曼树的基本操作。

2、  掌握哈夫曼编码的实现以及实际应用。

3、  加深对哈夫曼树、哈夫曼编码的理解,逐步培养解决实际问题的编程能力。

 

二、实验环境

Windows 7 + Visual Studio 2008

 

三、实验内容和步骤

实验内容:

利用哈夫曼编码进行通信可以大大提高信道利用率,缩短信息传输时间,降低传输成本。但是,这要求发送端通过一个编码系统对数据进行编码,在接受端将传来的数据进行译码。试为这样的信息收发站写一个哈夫曼编码/译码系统。

本系统应实现以下功能:(功能13必做,4为选做,请课后自行完成)

1  初始化:字符集(字母az,空格)共27个字符,以及其权值。建立哈夫曼树。并建立各个字符的哈夫曼编码。

2  打印字符集的哈夫曼编码。

3  编码:从终端读入字符串,实现该字符串的编码。

4  译码:实现刚才生成的哈夫曼编码还原为字符串。

 

实验步骤:

我先按照实验所说,实现了CreateHT()函数和CreateHCode()函数,即建立哈弗曼树函数以及创建哈弗曼编码函数。权值以指导书中给的为准。哈弗曼树以链式存储方式进行存储,每个结点有左儿子、右儿子的指针变量。在建立哈弗曼树时,以数据结构“优先队列”来存储结点的队列,并且最小权值的结点式中在队首。而“优先队列”则使用C++中的STL库。在进行创建哈弗曼编码的时候,采用了递归的方式,从根结点出发,一直查询到叶结点,并记录下路径即哈弗曼编码,以字符串映射(map)储存。哈弗曼编码结果与熊剑闻同学进行对比,发现无异。

完成了哈弗曼编码之后,开始进行对文件进行压缩解压。为了得到更多的权值,特下载了国外名著《洛丽塔》一书,并写下一段程序以取得各字符权值(权值见附录代码)。

压缩的算法如下:先取第一个字符,转化为哈弗曼编码二进制,若不足八位,则取下一个字符继续编码,直至达到八位,输出第一个压缩后的字符,如此循环,直至所有字符全被压缩。如a的哈弗曼编码为1010i的哈弗曼编码为0100,则经压缩后的ia字符串为(01001010)2即十进制的74即字符J,这样就实现了50%的压缩。当然,这样压缩出来的结果的字符不一定是可见字符,因为字符ASCII范围为0~255。最后再输出原文本的长度即可。而解压缩的过程则反一下,先从文件末尾读取原文本长度,然后输出原文本长度个字符。从第一个字符的第一位开始拼凑,然后第二位,一直拼凑到能找到对应的哈弗曼编码的字符为止,然后转而寻找第二个字符的哈弗曼编码。直至文本输出完毕为止。

这次哈弗曼编码实验,我以《洛丽塔》一书作为实验文本。原文大小为647,270 字节,压缩后的大小为370,847 字节,压缩率约为57.29%

 

    并且在解压缩后与原文作对比,发现无损。至此,试验完成。

 

四、遇到问题及解决方案

开始在压缩解压处总出问题,压缩过后的解压过程总是会缺少一些字符甚至大篇幅缺失。在重新理清思路之后,将Encode()Decode()函数删除重新编写一遍,以更精简的代码实现,结果对了。

 

五、实验感想

哈弗曼编码是一种常用的编码形式。甚至是WinRARZIP7z等压缩软件虽然不是是用哈弗曼编码进行压缩解压,毕竟纯哈弗曼编码的压缩率还是不足,但是至少它们多多少少都有点哈弗曼编码的思想。所以这是一种很不错的算法。

 

六、附件

Code:
  1. /**  
  2.  * @brief 哈夫曼编码  
  3.  * @author 朱凯迪  
  4.  * @date 2010-11-30  
  5.  */  
  6. #include <iostream>   
  7. #include <string>   
  8. #include <queue>   
  9. #include <map>   
  10. using namespace std;   
  11.   
  12. /**  
  13.  * @brief 哈弗曼结点  
  14.  * 记录了哈弗曼树结点的数据、权重及左右儿子  
  15.  */  
  16. struct HTNode {   
  17.     char data;   
  18.     HTNode *lc, *rc;   
  19.     int w;   
  20.   
  21.     /** 节点构造函数 */  
  22.     HTNode(char _d, int _w, HTNode *_l = NULL, HTNode *_r = NULL)   
  23.     {   
  24.         data = _d;   
  25.         w = _w;   
  26.         lc = _l;   
  27.         rc = _r;   
  28.     }   
  29.   
  30.     /** 节点拷贝构造函数 */  
  31.     HTNode(const HTNode &h)   
  32.     {   
  33.         data = h.data;   
  34.         lc = h.lc;   
  35.         rc = h.rc;   
  36.         w = h.w;   
  37.     }   
  38.   
  39.     /** 用于优先队列比较的运算符重载 */  
  40.     friend bool operator < (const HTNode &a, const HTNode &b)   
  41.     {   
  42.         return a.w > b.w;   
  43.     }   
  44. };   
  45.   
  46. /** 哈弗曼树叶子节点数、各叶子结点数据及权重 */  
  47. /** 权值从Lolita小说中抽样取出 */  
  48. const char ch[] = {    
  49.     10, 32, 33, 37, 40, 41, 44, 45, 46, 48,    
  50.     49, 50, 51, 52, 53, 54, 55, 56, 57, 58,    
  51.     59, 63, 65, 66, 67, 68, 69, 70, 71, 72,    
  52.     73, 74, 75, 76, 77, 78, 79, 80, 81, 82,    
  53.     83, 84, 85, 86, 87, 88, 89, 90, 91, 93,    
  54.     97, 98, 99, 100, 101, 102, 103, 104, 105, 106,    
  55.     107, 108, 109, 110, 111, 112, 113, 114, 115, 116,    
  56.     117, 118, 119, 120, 121, 122, 123, 161, 164, 166,    
  57.     168, 170, 173, 174, 175, 176, 177, 180, 186, 255,   
  58.     '/r''/0'  
  59.  };   
  60.   
  61. const int fnum[] = {    
  62.     2970, 99537, 265, 1, 496, 494, 9032, 1185, 5064, 108,    
  63.     180, 132, 99, 105, 82, 64, 62, 77, 126, 296,    
  64.     556, 548, 818, 443, 543, 435, 225, 271, 260, 797,    
  65.     3487, 158, 50, 1053, 589, 498, 332, 316, 61, 276,    
  66.     724, 855, 54, 293, 543, 11, 185, 11, 25, 26,    
  67.     42416, 7856, 12699, 23670, 61127, 10229, 10651, 27912, 32809, 510,    
  68.     4475, 23812, 13993, 34096, 38387, 9619, 500, 30592, 30504, 42377,    
  69.     14571, 4790, 11114, 769, 10394, 611, 1, 4397, 12, 71,    
  70.     117, 1234, 81, 5, 852, 1116, 1109, 1, 3, 1,   
  71.     2970   
  72.  };   
  73.   
  74. const int n = 91;   
  75.   
  76.   
  77. /** 优先队列 */  
  78. priority_queue<HTNode> pq;   
  79.   
  80. /** 哈弗曼编码映射 */  
  81. map<string, char> dcode;   
  82. map<char, string> ecode;   
  83.   
  84. /** 根节点以及总权重+边长 */  
  85. HTNode *root;   
  86. int sum = 0;   
  87.   
  88. /** 初始化叶节点,并加入到优先队列中 */  
  89. void Init()   
  90. {   
  91.     for(int i = 0; i < n; i++)   
  92.     {   
  93.         HTNode p(ch[i], fnum[i]);   
  94.         pq.push(p);   
  95.     }   
  96. }   
  97.   
  98. /** 建立哈夫曼树 */  
  99. void CreateHT()   
  100. {   
  101.     HTNode *lmin, *rmin;   
  102.   
  103.     /** 当队列中不止一个元素时 */  
  104.     while(!pq.empty() && 1 != pq.size())   
  105.     {   
  106.         /** 取队首两个元素(权值最小) */  
  107.         lmin = new HTNode(pq.top());   
  108.         pq.pop();   
  109.         rmin = new HTNode(pq.top());   
  110.         pq.pop();   
  111.   
  112.         /** 合并元素重新入队 */  
  113.         HTNode p(0, lmin->w + rmin->w, lmin, rmin);   
  114.         pq.push(p);   
  115.     }   
  116.   
  117.     if(!pq.empty())    
  118.     {   
  119.         /** 根节点 */  
  120.         root = new HTNode(pq.top());   
  121.         pq.pop();   
  122.     }   
  123. }   
  124.   
  125. /** 创建哈夫曼编码 */  
  126. void CreateHTCode(HTNode *p, string str)   
  127. {   
  128.     if(!p) return;   
  129.     if(0 != p->data)   
  130.     {   
  131.         /** 若是叶节点,则记录此节点的编码值 */  
  132.         dcode[str] = p->data;   
  133.         ecode[p->data] = str;   
  134.         sum += (str.length() * p->w);   
  135.   
  136.         return;   
  137.     }   
  138.   
  139.     CreateHTCode(p->lc, str + "0");   
  140.     CreateHTCode(p->rc, str + "1");   
  141. }   
  142.   
  143. /** 显示哈夫曼编码 */  
  144. void DispCode()   
  145. {   
  146.     printf("输出哈弗曼编码:/n");   
  147.     for(int i = 0; i < n; i++)   
  148.     {   
  149.         printf("/t'%c':/t%s/n", ch[i], ecode[ch[i]].c_str());   
  150.     }   
  151.     printf("平均长度:%.5lf/n"double(sum) / double(root->w));   
  152. }   
  153.   
  154. /** 释放哈夫曼树 */  
  155. void Release(HTNode *p)   
  156. {   
  157.     if(!p) return;   
  158.     Release(p->lc);   
  159.     Release(p->rc);   
  160.     delete p;   
  161. }   
  162.   
  163. /** 输出压缩文 */  
  164. void putEncode(FILE *fp, char *buf)   
  165. {   
  166.     unsigned char code = 0;   
  167.     for(int i = 0; i < 8; i++)   
  168.         code = (code << 1) + (buf[i] - '0');   
  169.        
  170.     fwrite(&code, sizeof(unsigned char), 1, fp);   
  171. }   
  172.   
  173. /** 判断是否在字符串内 */  
  174. bool instr(char c, const char str[])   
  175. {   
  176.     for(int i = 0; i < strlen(str); i++)   
  177.         if(c == str[i]) return true;   
  178.   
  179.     return false;   
  180. }   
  181.   
  182. /** 压缩文件 */  
  183. void Encode()   
  184. {   
  185.     FILE *OF;   
  186.     FILE *IF;   
  187.     char ifn[255];   
  188.     const char ofn[] = "Encode.txt";   
  189.     char buf[9];   
  190.     int cnt = 0, newcnt = 0;   
  191.   
  192.     printf("Input the filename: ");   
  193.     scanf("%s", ifn);   
  194.   
  195.     IF = fopen(ifn, "rb");   
  196.     if(!IF)   
  197.     {   
  198.         printf("Wrong file./n");   
  199.         return;   
  200.     }   
  201.   
  202.     OF = fopen(ofn, "wb+");   
  203.     if(!OF)   
  204.     {   
  205.         printf("Wrong file./n");   
  206.     }   
  207.   
  208.     /** 开始读文件 */  
  209.     memset(buf, 0, sizeof(buf));   
  210.     while(!feof(IF))   
  211.     {   
  212.         unsigned char c;   
  213.         fread(&c, sizeof(unsigned char), 1, IF);   
  214.         if(instr(c, ch));   
  215.         else c = ' ';   
  216.         for(int i = 0; i < ecode[c].length(); i++)   
  217.         {   
  218.             buf[strlen(buf)] = ecode[c][i];   
  219.             if(8 == strlen(buf))    
  220.             {   
  221.                 newcnt++;   
  222.                 putEncode(OF, buf);   
  223.                 memset(buf, 0, sizeof(buf));   
  224.             }   
  225.         }   
  226.   
  227.         cnt++;   
  228.     }   
  229.   
  230.     cnt--;   
  231.     if(0 != strlen(buf))   
  232.     {   
  233.         for(int i = strlen(buf); i < 8; i++) buf[i] = '0';   
  234.         putEncode(OF, buf);   
  235.     }   
  236.   
  237.     fwrite(&cnt, 4, 1, OF);   
  238.   
  239.     fclose(IF);   
  240.     fclose(OF);   
  241.   
  242.     printf("压缩成功!压缩率:%.2f%c/n", (((double)newcnt + 4.0f) / (double)cnt) * 100, '%');   
  243. }   
  244.   
  245. /** 补0 */  
  246. void putZeros(char *buf)   
  247. {   
  248.     char tmpbuf[9];   
  249.     memset(tmpbuf, 0, sizeof(tmpbuf));   
  250.     if(8 != strlen(buf))   
  251.     {   
  252.         for(int i = 0; i < 8 - strlen(buf); i++) tmpbuf[i] = '0';   
  253.         strcat(tmpbuf, buf);   
  254.         strcpy(buf, tmpbuf);   
  255.     }   
  256. }   
  257.   
  258. /** 解压缩 */  
  259. void Decode()   
  260. {   
  261.     char buf[256];   
  262.     char oldbuf[9];   
  263.     const char ifn[] = "Encode.txt";   
  264.     const char ofn[] = "Decode.txt";   
  265.     FILE *IF = fopen(ifn, "rb");   
  266.     if(!IF)    
  267.     {   
  268.         printf("Wrong file./n");   
  269.         return;   
  270.     }   
  271.        
  272.     FILE *OF = fopen(ofn, "wb+");   
  273.     if(!OF)   
  274.     {   
  275.         printf("Wrong file./n");   
  276.         return;   
  277.     }   
  278.   
  279.     int tot, cnt = 0;   
  280.     fseek(IF, -4L, SEEK_END);   
  281.     fread(&tot, 4, 1, IF);   
  282.     fseek(IF, 0L, SEEK_SET);   
  283.   
  284.     memset(buf, 0, sizeof(buf));   
  285.     while(true)   
  286.     {   
  287.         if(cnt == tot) break;   
  288.         unsigned char c;   
  289.         fread(&c, sizeof(unsigned char), 1, IF);   
  290.         itoa(c, oldbuf, 2);   
  291.         putZeros(oldbuf);   
  292.   
  293.         for(int i = 0; i < 8; i++)   
  294.         {   
  295.             if(cnt == tot) break;   
  296.             buf[strlen(buf)] = oldbuf[i];   
  297.             if(dcode.end() != dcode.find(string(buf)))   
  298.             {   
  299.                 fwrite(&dcode[string(buf)], sizeof(char), 1, OF);   
  300.                 memset(buf, 0, sizeof(buf));   
  301.                 cnt++;   
  302.             }   
  303.         }   
  304.     }   
  305.   
  306.     fclose(IF);   
  307.     fclose(OF);   
  308.   
  309.     printf("解压成功!文件名Decode.txt。/n");   
  310. }   
  311.   
  312. int main()   
  313. {   
  314.     Init();   
  315.     CreateHT();   
  316.     CreateHTCode(root, "");   
  317.     DispCode();   
  318.     Encode();   
  319.     Decode();   
  320.     Release(root);   
  321.     system("pause");   
  322.   
  323.     return 0;   
  324. }   

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值