问题描述
利用哈夫曼编码进行信息通信可以较大提高信息利用率,缩短信息传输时间,降低传输成本。但是,这要求在发送端通过一个编码系统对待数据预先编码留在接收端将传来的数据进行译码。对于双工通道(即可以双向传输信息的通道),每端都需要一个完整的编 / 译码系统。试为这样的信息收发站写一个哈夫曼码的编译码系统。该系统应具有一下功能:
(1)初始化:从终端读入字符集大小n,及n个字符和m个权值,建立哈夫曼树,并将其保存于磁盘huffman文件中。
(2)编码:利用已建好的哈夫曼树,对待发送电文进行编码,然后将结果保存于磁盘文件codefile中。
(3)解码:利用已建好的哈夫曼树,对文件codefile中的代码进行译码,结果存入文件textfile中。
(4)打印代码文件:将文件codefile显示在终端上,每行50个代码。同时将此字符形式的编码文件写入文件codeprint中。
(5)打印哈夫曼树:将已在内存中的哈夫曼树以直观的形式显示在终端上,同时将此字符形式的哈夫曼树写入文件treeprint中。
测试数据
在tobetrans.dat文件中输入“THIS PROGRAM IS MY FAVORITE”,字符集和其频度如表11-2所示。
字符 | _ | A | B | C | D | E | F | G | H | I | J | K | L | M |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
频度 | 186 | 64 | 23 | 22 | 32 | 103 | 21 | 12 | 47 | 57 | 1 | 5 | 32 | 20 |
字符 | N | O | P | Q | R | S | T | U | V | W | X | Y | Z | |
频度 | 20 | 56 | 19 | 2 | 50 | 51 | 55 | 30 | 10 | 11 | 2 | 21 | 2 |
解决思路
根据问题的描述,我们可以清晰的认识到,解决问题的关键在于哈夫曼树和对应的哈夫曼编码表的构建。如果哈夫曼树和哈夫曼编码表初始化成功,编码只需要按照自己构建的哈夫曼编码表得到对应的二进制编码,解码也只需要与哈夫曼编码表一一对应得到相应的字符。打印代码文件需要用到文件的读操作,打印哈夫曼树可以用先序遍历的方式将其打印出来。
对于哈夫曼树和对应的哈夫曼编码的构建,可参考我的另一篇博客“哈夫曼树及其应用”。
哈夫曼树及其应用:https://blog.csdn.net/qq_42096498/article/details/93192448
下面进行每一个功能模块的代码展示和实例演示。
功能模块展示
结构体预定义
代码:
//哈夫曼树结构体
#define s 300
typedef struct{
int weight;//权值,也即频率
int plink, llink, rlink;//双亲及左右孩子指针,静态指针
}node;
node tree[s];//存储树结点
//哈夫曼编码表结构体
typedef struct{
int start = 99;//存放起始位置
int bits[100];//存放编码位串
}codetype;
typedef struct{
char symbol;//字符类型
codetype code;//二进制编码
}element;
element table[100];//存储哈夫曼编码表的每一个字符及其对应的二进制编码
int pre[s], in[s];//存储前序和中序遍历的元素
第一个模块:初始化模块
代码:
/*
功能:初始化哈夫曼树,并将其保存在huffman文件中,。
传入参数:字符种类n,字符类型数组ch[],对应的字符权值数组weight[]。
*/
void InitHaff(int n, int weight[], char ch[]){
int m = n*2 - 1;
int x1, x2;
int q = 0;
ofstream file("huffman.txt");//打开文件
int visited[m] = {0};//判断结点是否已经被合并
//将tree初始化,即每个结点的三个指针都置为-1
for(int i=0; i<m; i++){
tree[i].plink = tree[i].rlink = tree[i].llink = -1;
}
//前n个tree赋初值,对table(哈夫曼编码表)赋初值
for(int i=0; i<n; i++){
tree[i].weight = weight[i];
table[i].symbol = ch[i];
}
for(int i=n; i<m; i++){
//对x1,x2初始化
for(int j=0; j<i; j++){
if(visited[j]==0)
x1 = x2 = j;
}
//从i-1中选取权值最小的两个结点
for(int j=0; j<i; j++){
if(tree[x1].weight > tree[j].weight && visited[j]==0){
x1 = j;
}
}
for(int j=0; j<i; j++){
if(tree[x2].weight > tree[j].weight && j!=x1 && visited[j]==0){
x2 = j;
}
}
visited[x2] = 1;
visited[x1] = 1;
//将权值最小和次小的结点合并,最小的是左结点,次小的是右结点
tree[x1].plink = i;
tree[x2].plink = i;
tree[i].llink = x1;
tree[i].rlink = x2;
tree[i].weight = tree[x1].weight + tree[x2].weight;
}
//初始化结束
//将哈夫曼树存入文件中,使用前序遍历和中序遍历的方式存储
//前序遍历
preorder(m-1, pre, &q);
q = 0;
//中序遍历
inorder(m-1, in, &q);
// for(int i=0; i<m; i++){
// cout<<pre[i]<<" ";
// }
// cout<<endl;
// for(int i=0; i<m; i++){
// cout<<in[i]<<" ";
// }
// cout<<endl;
if(file.fail()){
cout<<"打开文件失败。"<<endl;
exit(0);
}
file<<"前序遍历:\n";
for(int i=0; i<m; i++){
file<<pre[i]<<" ";
}
file<<"\n";
file<<"中序遍历:\n";
for(int i=0; i<m; i++){
file<<in[i]<<" ";
}
file<<"\n";
file<<"叶子结点对应的权值和字符类型:\n";
for(int i; i<n; i++){
file<<i+1<<"-"<<tree[i].weight<<"-"<<table[i].symbol<<" ";
}
file.close();
cout<<"哈夫曼树保存于huffman.txt文件成功。"<<endl;
//读入文件结束
//构建哈夫曼编码表
cout<<"构建哈夫曼编码表。"<<endl;
for(int i=0; i<n; i++){
codetype c;
int f=i;
while(f != m-1){
int a = f;
f = tree[f].plink;
c.bits[c.start--] = (a==tree[f].llink)?0:1;
}
c.start = c.start + 1;
// 验证是否正确
// cout<<c.start<<endl;
cout<<table[i].symbol<<" ";
for(int j=c.start; j<100; j++){
cout<<c.bits[j];
}
cout<<endl;
table[i].code = c;
}
cout<<"由哈夫曼树得到的哈弗曼编码表成功。"<<endl;
cout<<"初始化成功!\n"<<endl;
//构建成功
}
上述代码分别完成了哈夫曼树的构建和对应字符的哈夫曼编码表的构建。
运行结果:
第二个模块:编码模块
代码:
//对tobetrans.dat进行编码 ,传入参数:字符种类数n
void code(int n){
char str[1000];//存储文件中读取的字符串
int num=0;
ifstream file1("tobetrans.dat");//打开文件
ofstream file2("codefile.txt");
if(file1.fail() || file2.fail()){
cout<<"打开文件失败。"<<endl;
exit(0);
}
file1.getline(str, 1000);//从type 文件中读入一行
// file>>str;//见空格停止,不一定读入一行
cout<<"文件中的内容:"<<str;
while(str[num]!='#'){
codetype c;
// cout<<str[num]<<endl;
for(int i=0; i<n; i++){
//当str内的字符与编码表对应的字符相同时
if(table[i].symbol == str[num]){
c = table[i].code;
// cout<<c.start<<endl;
for(int j=c.start; j<100; j++){
file2<<c.bits[j];
// cout<<c.bits[j];
}
// cout<<endl;
}
}
num++;
}
//codefile.txt文件以'#'结尾
file2<<'#';
file1.close();
file2.close();
}
运行结果:
第三个模块:译码模块
代码:
/*
对文件codefile中的代码进行解码 ,结果存入文件textfile中
传入参数:字符种类n,哈夫曼树的所有结点数m(等于2*n-1)
*/
void decode(int n, int m){
char str[10000];//存储codefile.txt的0-1编码
int t = m-1;//结点编号
int num=;
ifstream file1("codefile.txt");
ofstream file2("textfile.txt");
if(file1.fail() || file2.fail()){
cout<<"打开文件失败。"<<endl;
exit(0);
}
file1.getline(str, 10000);
while(str[num]!='#'){
t = (str[num]=='0')?tree[t].llink:tree[t].rlink;
num++;
//到达叶子结点
if(tree[t].llink == -1){
cout<<table[t].symbol;
file2<<table[t].symbol;
t = m-1;
if(str[num] == '#')
break;
}
}
cout<<endl;
file1.close();
file2.close();
}
运行结果:
第四个模块:打印代码文件模块
代码:
//打印代码文件
void ptxt(){
char str[10000];//存储codefile.txt的0-1编码
int num = 0;
ifstream file1("codefile.txt");
if(file1.fail()){
cout<<"打开文件失败。"<<endl;
exit(0);
}
file1.getline(str, 10000);
while(str[num]!='#'){
cout<<str[num++];
if(num%50==0)
cout<<endl;
}
cout<<endl;
file1.close();
}
运行结果:
第五个模块:打印哈夫曼树模块
代码:
//前序遍历
void preorder(int x, int pre[], int *i){
if(x != -1){
pre[*i] = x;
*i += 1;
preorder(tree[x].llink, pre, i);
preorder(tree[x].rlink, pre, i);
}
}
//打印哈夫曼树
void ptree(int n, int m){
cout<<"前序遍历:";
for(int i=0; i<m; i++){
cout<<pre[i]<<" ";
}
cout<<endl;
}
运行结果:
完整代码及相应的文件存放在github中。github:https://github.com/bboy12345/huffman-coding-and-decoding-system.git