数据结构实验报告之《哈弗曼编码》
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;
}
科目: 数据结构 姓名: 朱凯迪 实验日期: 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;
}
相关文章推荐
- 排序二叉树、查找、二分法查找、数据结构,实验报告
- 农夫过河【数据结构实验报告】
- 数据结构实验一(实验报告)
- 数据结构实验报告-图算法-最小生成树-最短路-拓扑排序-搜索
- 【线性表二:】大学数据结构实验报告中的两三个关于线性表的小算法
- java项目——数据结构实验报告
- 数据结构实验报告
- C++数据结构实验报告:顺序表实现
- 数据结构实验一 实验报告
- 数据结构——VC环境实验一实验报告
- 数据结构实验报告
- C++数据结构实验一实验报告
- 数据结构实验报告四(二叉树…
- 数据结构实验报告三:教材3.10Josephus(约瑟夫环)问题、多项式乘法问题的求解
- 数据结构实验报告(一)
- 线性表的相关操作 数据结构实验报告第二个
- 【实验报告】数据结构实验二:线性表的实验
- 数据结构实验之图论三:判断可达性
- 数据结构实验之图论六:村村通公路
- 数据结构实验之图论五:从起始点到目标点的最短步数(BFS)