您的位置:首页 > 理论基础 > 数据结构算法

数据结构实验报告之《哈弗曼编码》

2010-12-06 01:21 399 查看
实验报告[/b]
科目: 数据结构 姓名: 朱凯迪 实验日期: 2010-12-29 ;
实验名称:           哈弗曼编码             ;


一、实验目的
1、 熟悉哈夫曼树的基本操作。
2、 掌握哈夫曼编码的实现以及实际应用。
3、 加深对哈夫曼树、哈夫曼编码的理解,逐步培养解决实际问题的编程能力。

二、实验环境
Windows 7 + Visual Studio 2008

三、实验内容和步骤
实验内容:
利用哈夫曼编码进行通信可以大大提高信道利用率,缩短信息传输时间,降低传输成本。但是,这要求发送端通过一个编码系统对数据进行编码,在接受端将传来的数据进行译码。试为这样的信息收发站写一个哈夫曼编码/译码系统。
本系统应实现以下功能:(功能1~3必做,4为选做,请课后自行完成)
(1) 初始化:字符集(字母a~z,空格)共27个字符,以及其权值。建立哈夫曼树。并建立各个字符的哈夫曼编码。
(2) 打印字符集的哈夫曼编码。
(3) 编码:从终端读入字符串,实现该字符串的编码。
(4) 译码:实现刚才生成的哈夫曼编码还原为字符串。

实验步骤:
我先按照实验所说,实现了CreateHT()函数和CreateHCode()函数,即建立哈弗曼树函数以及创建哈弗曼编码函数。权值以指导书中给的为准。哈弗曼树以链式存储方式进行存储,每个结点有左儿子、右儿子的指针变量。在建立哈弗曼树时,以数据结构“优先队列”来存储结点的队列,并且最小权值的结点式中在队首。而“优先队列”则使用C++中的STL库。在进行创建哈弗曼编码的时候,采用了递归的方式,从根结点出发,一直查询到叶结点,并记录下路径即哈弗曼编码,以字符串映射(map)储存。哈弗曼编码结果与熊剑闻同学进行对比,发现无异。
完成了哈弗曼编码之后,开始进行对文件进行压缩解压。为了得到更多的权值,特下载了国外名著《洛丽塔》一书,并写下一段程序以取得各字符权值(权值见附录代码)。
压缩的算法如下:先取第一个字符,转化为哈弗曼编码二进制,若不足八位,则取下一个字符继续编码,直至达到八位,输出第一个压缩后的字符,如此循环,直至所有字符全被压缩。如a的哈弗曼编码为1010,i的哈弗曼编码为0100,则经压缩后的”ia”字符串为(01001010)2即十进制的74即字符’J’,这样就实现了50%的压缩。当然,这样压缩出来的结果的字符不一定是可见字符,因为字符ASCII范围为0~255。最后再输出原文本的长度即可。而解压缩的过程则反一下,先从文件末尾读取原文本长度,然后输出原文本长度个字符。从第一个字符的第一位开始拼凑,然后第二位,一直拼凑到能找到对应的哈弗曼编码的字符为止,然后转而寻找第二个字符的哈弗曼编码。直至文本输出完毕为止。
这次哈弗曼编码实验,我以《洛丽塔》一书作为实验文本。原文大小为647,270 字节,压缩后的大小为370,847 字节,压缩率约为57.29%。

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

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

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

六、附件

Code:

/**

* @brief 哈夫曼编码

* @author 朱凯迪

* @date 2010-11-30

*/

#include <iostream>

#include <string>

#include <queue>

#include <map>

using namespace std;

/**

* @brief 哈弗曼结点

* 记录了哈弗曼树结点的数据、权重及左右儿子

*/

struct HTNode {

char data;

HTNode *lc, *rc;

int w;

/** 节点构造函数 */

HTNode(char _d, int _w, HTNode *_l = NULL, HTNode *_r = NULL)

{

data = _d;

w = _w;

lc = _l;

rc = _r;

}

/** 节点拷贝构造函数 */

HTNode(const HTNode &h)

{

data = h.data;

lc = h.lc;

rc = h.rc;

w = h.w;

}

/** 用于优先队列比较的运算符重载 */

friend bool operator < (const HTNode &a, const HTNode &b)

{

return a.w > b.w;

}

};

/** 哈弗曼树叶子节点数、各叶子结点数据及权重 */

/** 权值从Lolita小说中抽样取出 */

const char ch[] = {

10, 32, 33, 37, 40, 41, 44, 45, 46, 48,

49, 50, 51, 52, 53, 54, 55, 56, 57, 58,

59, 63, 65, 66, 67, 68, 69, 70, 71, 72,

73, 74, 75, 76, 77, 78, 79, 80, 81, 82,

83, 84, 85, 86, 87, 88, 89, 90, 91, 93,

97, 98, 99, 100, 101, 102, 103, 104, 105, 106,

107, 108, 109, 110, 111, 112, 113, 114, 115, 116,

117, 118, 119, 120, 121, 122, 123, 161, 164, 166,

168, 170, 173, 174, 175, 176, 177, 180, 186, 255,

'/r', '/0'

};

const int fnum[] = {

2970, 99537, 265, 1, 496, 494, 9032, 1185, 5064, 108,

180, 132, 99, 105, 82, 64, 62, 77, 126, 296,

556, 548, 818, 443, 543, 435, 225, 271, 260, 797,

3487, 158, 50, 1053, 589, 498, 332, 316, 61, 276,

724, 855, 54, 293, 543, 11, 185, 11, 25, 26,

42416, 7856, 12699, 23670, 61127, 10229, 10651, 27912, 32809, 510,

4475, 23812, 13993, 34096, 38387, 9619, 500, 30592, 30504, 42377,

14571, 4790, 11114, 769, 10394, 611, 1, 4397, 12, 71,

117, 1234, 81, 5, 852, 1116, 1109, 1, 3, 1,

2970

};

const int n = 91;

/** 优先队列 */

priority_queue<HTNode> pq;

/** 哈弗曼编码映射 */

map<string, char> dcode;

map<char, string> ecode;

/** 根节点以及总权重+边长 */

HTNode *root;

int sum = 0;

/** 初始化叶节点,并加入到优先队列中 */

void Init()

{

for(int i = 0; i < n; i++)

{

HTNode p(ch[i], fnum[i]);

pq.push(p);

}

}

/** 建立哈夫曼树 */

void CreateHT()

{

HTNode *lmin, *rmin;

/** 当队列中不止一个元素时 */

while(!pq.empty() && 1 != pq.size())

{

/** 取队首两个元素(权值最小) */

lmin = new HTNode(pq.top());

pq.pop();

rmin = new HTNode(pq.top());

pq.pop();

/** 合并元素重新入队 */

HTNode p(0, lmin->w + rmin->w, lmin, rmin);

pq.push(p);

}

if(!pq.empty())

{

/** 根节点 */

root = new HTNode(pq.top());

pq.pop();

}

}

/** 创建哈夫曼编码 */

void CreateHTCode(HTNode *p, string str)

{

if(!p) return;

if(0 != p->data)

{

/** 若是叶节点,则记录此节点的编码值 */

dcode[str] = p->data;

ecode[p->data] = str;

sum += (str.length() * p->w);

return;

}

CreateHTCode(p->lc, str + "0");

CreateHTCode(p->rc, str + "1");

}

/** 显示哈夫曼编码 */

void DispCode()

{

printf("输出哈弗曼编码:/n");

for(int i = 0; i < n; i++)

{

printf("/t'%c':/t%s/n", ch[i], ecode[ch[i]].c_str());

}

printf("平均长度:%.5lf/n", double(sum) / double(root->w));

}

/** 释放哈夫曼树 */

void Release(HTNode *p)

{

if(!p) return;

Release(p->lc);

Release(p->rc);

delete p;

}

/** 输出压缩文 */

void putEncode(FILE *fp, char *buf)

{

unsigned char code = 0;

for(int i = 0; i < 8; i++)

code = (code << 1) + (buf[i] - '0');

fwrite(&code, sizeof(unsigned char), 1, fp);

}

/** 判断是否在字符串内 */

bool instr(char c, const char str[])

{

for(int i = 0; i < strlen(str); i++)

if(c == str[i]) return true;

return false;

}

/** 压缩文件 */

void Encode()

{

FILE *OF;

FILE *IF;

char ifn[255];

const char ofn[] = "Encode.txt";

char buf[9];

int cnt = 0, newcnt = 0;

printf("Input the filename: ");

scanf("%s", ifn);

IF = fopen(ifn, "rb");

if(!IF)

{

printf("Wrong file./n");

return;

}

OF = fopen(ofn, "wb+");

if(!OF)

{

printf("Wrong file./n");

}

/** 开始读文件 */

memset(buf, 0, sizeof(buf));

while(!feof(IF))

{

unsigned char c;

fread(&c, sizeof(unsigned char), 1, IF);

if(instr(c, ch));

else c = ' ';

for(int i = 0; i < ecode[c].length(); i++)

{

buf[strlen(buf)] = ecode[c][i];

if(8 == strlen(buf))

{

newcnt++;

putEncode(OF, buf);

memset(buf, 0, sizeof(buf));

}

}

cnt++;

}

cnt--;

if(0 != strlen(buf))

{

for(int i = strlen(buf); i < 8; i++) buf[i] = '0';

putEncode(OF, buf);

}

fwrite(&cnt, 4, 1, OF);

fclose(IF);

fclose(OF);

printf("压缩成功!压缩率:%.2f%c/n", (((double)newcnt + 4.0f) / (double)cnt) * 100, '%');

}

/** 补0 */

void putZeros(char *buf)

{

char tmpbuf[9];

memset(tmpbuf, 0, sizeof(tmpbuf));

if(8 != strlen(buf))

{

for(int i = 0; i < 8 - strlen(buf); i++) tmpbuf[i] = '0';

strcat(tmpbuf, buf);

strcpy(buf, tmpbuf);

}

}

/** 解压缩 */

void Decode()

{

char buf[256];

char oldbuf[9];

const char ifn[] = "Encode.txt";

const char ofn[] = "Decode.txt";

FILE *IF = fopen(ifn, "rb");

if(!IF)

{

printf("Wrong file./n");

return;

}

FILE *OF = fopen(ofn, "wb+");

if(!OF)

{

printf("Wrong file./n");

return;

}

int tot, cnt = 0;

fseek(IF, -4L, SEEK_END);

fread(&tot, 4, 1, IF);

fseek(IF, 0L, SEEK_SET);

memset(buf, 0, sizeof(buf));

while(true)

{

if(cnt == tot) break;

unsigned char c;

fread(&c, sizeof(unsigned char), 1, IF);

itoa(c, oldbuf, 2);

putZeros(oldbuf);

for(int i = 0; i < 8; i++)

{

if(cnt == tot) break;

buf[strlen(buf)] = oldbuf[i];

if(dcode.end() != dcode.find(string(buf)))

{

fwrite(&dcode[string(buf)], sizeof(char), 1, OF);

memset(buf, 0, sizeof(buf));

cnt++;

}

}

}

fclose(IF);

fclose(OF);

printf("解压成功!文件名Decode.txt。/n");

}

int main()

{

Init();

CreateHT();

CreateHTCode(root, "");

DispCode();

Encode();

Decode();

Release(root);

system("pause");

return 0;

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: