前言:必须先理解Huffuman树的构建过程,可以参考其他人写的博客:
哈夫曼树以及哈夫曼编码的构造步骤
实验四 Huffman编码
一、实验目的
1、理解哈夫曼树及其应用。
2、掌握生成哈夫曼树的算法。
二、实验内容
设字符集为26个英文字母,其出现频度如下表所示。以二叉链表作存储结构,编写程序,实现如下的功能:
1、根据所提供的字母数据建立一个Huffman树;
2、根据生成的Huffman树的结构,显示输出所有字母的Huffman编码。
3、(选作内容)根据产生的Huffman编码,实现Huffman编/译码器。
三、 实验要求
1.认真阅读和掌握本实验的算法。
2.上机将本算法实现。
3.在程序的编写中尽量与专业的编程规范靠拢,系统代码采用结构化的编程方式,力求设计代码以及注释等规范,
4.保存和打印出程序的运行结果,并结合程序进行分析。
四、实验代码
列出了三种写法
// huffuman树是最小路径之和,所以huffuman树是不唯一的
// 每合并两个节点就得对权值重新进行排序,所以这里可以利用优先队列(最小堆)每次队头(堆顶)元素就是就是最小值
// 也可以用数组
// 输入格式
// 第一行包含一个整数n,表示输入的字符数量
// 第二行包含2n个字符,分别表示输入的字母以及出现次数,输入格式如下
// 26
// a 168 b 13 c 22 d 32 e 103 f 21 g 15 h 47 i 57 j 1 k 5 l 32 m 20 n 57 o 63 p 15 q 1 r 48 s 51 t 80 u 23 v 8 w 18 x 1 y 16 z 1
/*
//写法1:利用优先队列实现
#include <iostream>
#include <queue>
#include <algorithm>
using namespace std;
string strs[26];//存放最终每个字母生成的huffuman编码
struct Node{
//定义结构体用来生成节点
int val;
char word;
string c;//存储生成的Huffman编码
Node* left;
Node* right;
};
struct cmp {
//重载 () 运算符
bool operator ()(const Node* a, const Node* b) {
return a->val>b->val;
}
};
//根据生产的huffuman树生产每个字母的huffuman编码
void PreOrder(Node* root,string s)//这里是前序遍历
{
if(root==NULL)
return ;
root->c=s;
if(root->word!=' ')//只添加叶子节点也就是字母所在的节点
strs[root->word-'a']=s;
PreOrder(root->left,s+"0");//往左走则添加0
PreOrder(root->right,s+"1");//往右走则添加1
}
string stringToHufuman(string words)//huffuman编码器
{
string ans="";
for(int i=0;i<words.length();i++)
ans+=strs[words[i]-'a'];
return ans;
}
string huffumanToString(string password,Node* root)//huffuman解码器,解码Huffuman编码
{
string ans="";
Node* head=root;
for(int i=0;i<password.length();i++)
{
if(head->left==NULL&&head->right==NULL)//找到一个字母的Huffuman编码
{
ans+=head->word;
head=root;//将head重置为root,开启下一轮查找
}
if(password[i]=='0')
head=head->left;
else
head=head->right;
}
if(head->left==NULL&&head->right==NULL)//还需判断一下不然会漏掉最后一个字母
ans+=head->word;
return ans;
}
int main()
{
int n;
cin>>n;
//定义一个优先队列(最小堆)
priority_queue<Node*,vector<Node*>,cmp> q;
for(int i=0;i<n;i++)
{
char word;
int cnt;
cin>>word>>cnt;
Node* node=new Node{cnt,word,"",NULL,NULL};
q.push(node);
}
while(q.size()>1)//如果只剩下一个节点那么就是最终的根节点
{
Node* parent=new Node{};
Node* node1=q.top();
q.pop();
Node* node2=q.top();
q.pop();
parent->word=' ';//将合成得出来的节点字母设置为空格
parent->right=node1;//出现次数最少的字母放在右边(也可以放在左边,放在左边和放在右边产生的编码正好相反)
parent->left=node2;
parent->val=node1->val+node2->val;
q.push(parent);
}
for(int i=0;i<26;i++)
strs[i]="";
PreOrder(q.top(),"");
for(int i=0;i<26;i++)
{
if(strs[i]!="")
cout<<char (i+'a')<<"的huffuman编码为:"<<strs[i]<<endl;
}
cout<<stringToHufuman("zhongnandaxue")<<endl;
cout<<huffumanToString("00101101011100010001110101100111000011100110000001011011011010100",q.top())<<endl;
}
*/
/*
//写法2:利用结构体数组实现+自带排序函数sort实现
#include <iostream>
#include <queue>
#include <algorithm>
using namespace std;
string strs[26];//存放最终每个字母生成的huffuman编码,这里26只考虑了小写字母
struct Node{
//定义结构体用来生成节点
int val;//出现频率
char word;//存储字母
string c;//存储生成的Huffman编码
Node* left;
Node* right;
//重载操作<,这样利用c++自带的sort函数可以实现依据val的值从小到大排序。也可以根据val的大小,手写一个结构体排序
bool operator <(const Node &a)const {
return val<a.val;
}
};
Node nodes[100];//定义结构体数组存储产生的节点
//根据生产的huffuman树生产每个字母的huffuman编码
void PreOrder(Node* root,string s)//这里是前序遍历
{
if(root==NULL)
return ;
root->c=s;
if(root->word!=' ')//只添加叶子节点也就是字母所在的节点
strs[root->word-'a']=s;
PreOrder(root->left,s+"0");//往左走则添加0
PreOrder(root->right,s+"1");//往右走则添加1
}
string stringToHufuman(string words)//huffuman编码器
{
string ans="";
for(int i=0;i<words.length();i++)
ans+=strs[words[i]-'a'];
return ans;
}
string huffumanToString(string password,Node* root)//huffuman解码器,解码Huffuman编码
{
string ans="";
Node* head=root;
for(int i=0;i<password.length();i++)
{
if(head->left==NULL&&head->right==NULL)//找到一个字母的Huffuman编码
{
ans+=head->word;
head=root;//将head重置为root,开启下一轮查找
}
if(password[i]=='0')
head=head->left;
else
head=head->right;
}
if(head->left==NULL&&head->right==NULL)//还需判断一下不然会漏掉最后一个字母
ans+=head->word;
return ans;
}
int main()
{
int n;
cin>>n;
for(int i=0;i<n;i++)
{
char word;
int cnt;
cin>>word>>cnt;
nodes[i]={cnt,word,"",NULL,NULL};
}
for(int i=n;i<100;i++)
nodes[i]={1000000000,' ',"",NULL,NULL};//将多余的节点val设置为一个很大的数就不会影响排序结果
sort(nodes,nodes+100);
int idx=0;//设置排序的偏移量
while(nodes[idx+1].val!=1e9)//至少还存在两个未合并的节点就一直循环
{
nodes[99].val=nodes[0+idx].val+nodes[1+idx].val;
nodes[99].c=' ';
nodes[99].left=&nodes[1+idx];//其次小的放在左子树
nodes[99].right=&nodes[0+idx];//最小的放在右子树
idx+=2;
sort(nodes+idx,nodes+100);
}
for(int i=0;i<26;i++)
strs[i]="";
Node* root=&nodes[idx];
PreOrder(root,"");
for(int i=0;i<26;i++)
{
if(strs[i]!="")
cout<<char (i+'a')<<"的huffuman编码为:"<<strs[i]<<endl;
}
cout<<stringToHufuman("zhongnandaxue")<<endl;
cout<<huffumanToString("00101101111100010001100101010110000011000111000001011011011010100",root)<<endl;
}
*/
//写法三:利用结构体数组实现+自定义排序实现
#include <iostream>
#include <queue>
#include <algorithm>
using namespace std;
string strs[26];//存放最终每个字母生成的huffuman编码,这里26只考虑了小写字母
struct Node{
//定义结构体用来生成节点
int val;//出现频率
char word;//存储字母
string c;//存储生成的Huffman编码
Node* left;
Node* right;
};
Node nodes[100];//定义结构体数组存储产生的节点
//自定义排序
void MySort(Node* p,int start,int end)//start表示排序起始位置,end表示排序结束位置
{
for(int i=start;i<end-1;i++)
{
for(int j=start;j<end-(i-start)-1;j++)
{
if(p[j].val>p[j+1].val)
{
Node temp=p[j];
p[j]=p[j+1];
p[j+1]=temp;
}
}
}
}
//根据生产的huffuman树生产每个字母的huffuman编码
void PreOrder(Node* root,string s)//这里是前序遍历
{
if(root==NULL)
return ;
root->c=s;
if(root->word!=' ')//只添加叶子节点也就是字母所在的节点
strs[root->word-'a']=s;
PreOrder(root->left,s+"0");//往左走则添加0
PreOrder(root->right,s+"1");//往右走则添加1
}
string stringToHufuman(string words)//huffuman编码器
{
string ans="";
for(int i=0;i<words.length();i++)
ans+=strs[words[i]-'a'];
return ans;
}
string huffumanToString(string password,Node* root)//huffuman解码器,解码Huffuman编码
{
string ans="";
Node* head=root;
for(int i=0;i<password.length();i++)
{
if(head->left==NULL&&head->right==NULL)//找到一个字母的Huffuman编码
{
ans+=head->word;
head=root;//将head重置为root,开启下一轮查找
}
if(password[i]=='0')
head=head->left;
else
head=head->right;
}
if(head->left==NULL&&head->right==NULL)//还需判断一下不然会漏掉最后一个字母
ans+=head->word;
return ans;
}
int main()
{
int n;
cin>>n;
for(int i=0;i<n;i++)
{
char word;
int cnt;
cin>>word>>cnt;
nodes[i]={cnt,word,"",NULL,NULL};
}
for(int i=n;i<100;i++)
nodes[i]={1000000000,' ',"",NULL,NULL};//将多余的节点val设置为一个很大的数就不会影响排序结果
MySort(nodes,0,100);
int idx=0;//设置排序的偏移量
while(nodes[idx+1].val!=1e9)//至少还存在两个未合并的节点就一直循环
{
nodes[99].val=nodes[0+idx].val+nodes[1+idx].val;
nodes[99].c=' ';
nodes[99].left=&nodes[1+idx];//其次小的放在左子树
nodes[99].right=&nodes[0+idx];//最小的放在右子树
idx+=2;
MySort(nodes,idx,100);
}
for(int i=0;i<26;i++)
strs[i]="";
Node* root=&nodes[idx];
PreOrder(root,"");
for(int i=0;i<26;i++)
{
if(strs[i]!="")
cout<<char (i+'a')<<"的huffuman编码为:"<<strs[i]<<endl;
}
cout<<stringToHufuman("zhongnandaxue")<<endl;
cout<<huffumanToString("00101101001100010001100101100110000011000111000001011010111010100",root)<<endl;
}