题目要求:
根据哈夫曼编码的原理,编写一个程序,在用户输入结点权值的基础上求赫夫曼编码,并能把给定的编码进行译码。
(1)初始化:从键盘输入一字符串(或读入一文件),统计出现的字符和每个字符出现的频率,将字符出现的频率作为结点的权值,建立哈夫曼树。对各个字符进行哈夫曼编码,最后打印输出字符及每个字符对应的哈夫曼编码。
(2)编码:利用已建好的哈夫曼树对“输入串”进行哈夫曼编码,最后打印输入串对应的哈夫曼编码(写入文件)。
(3)译码:利用已建好的哈夫曼树对给定的一串代码进行译码,并打印输出得到的字符串。(选作)
测试数据:对字符串{casbcatbsatbat}进行编码;对电文“1101000”译码。字符集D={ ?},出现频率为w={?}
思路:
用c++的类来完成,对于输入的字符串,我们要知道树中每个字符出现的频率,还有每个字符对应的编码,这中一一对应关系,我们用map完成。我们还需要记录哈夫曼树的根,便于对电文译码。
对于二叉树的构建,我们不用数组,就用二叉链表解决,每个节点对应的成员为字符、频率、左右孩子。
节点类:
//节点类
class HuffmanNode{
public:
char date;
int freq;
HuffmanNode *left;
HuffmanNode *right;
HuffmanNode(char date,int freq)
{
this->date=date;
this->freq=freq;
left=right=NULL;
}
};
哈夫曼树的数据成员
private:
map<char,int> freq; //字符--对应频次
map<char,string> codes; //字符--对应编码
HuffmanNode *root;
步骤一、构建哈夫曼树(用类的构造函数)。
先统计字符的种类和每种字符出现个数,随后以每一种字符构建一个节点,作为哈夫曼树的叶子,随后将这些节点都放进一个数据结构L中,当L中还有一个以上的节点时,找出权值(也就是频率)最小的A,和次最小的B。A,B的权值相加构成一个新节点C,C的左孩子为A,右孩子为B。随后C进入L,A、B从L中出来。当L中只剩下一个节点时,剩下的那个就是哈夫曼树的根。
现在的难点就是找出这么一种数据结构L,能随时拿出两个数据或加入一个数据,而且每次拿出的两个数据要是权值最小的,也就是权值最小的优先拿出。
课本上用线性表完成,利用叶子节点和二度节点的数量关系创建一个数组,每次找数组中没有父亲的权值最小的节点最小,这个找的过程很麻烦。
既然是权值最小的优先拿出,而c++stl库就有一种数据结构,能按照自己设定的优先级来处理对象---优先队列。我们只需要将权值小的放在堆顶,每次拿出堆顶的两个出来就行,就避免了找的这个过程。
Huffman(string s)
{//用构造函数来构造一颗哈夫曼数
//创建 字符---出现次数 的键值对
for(int i=0;i<s.size();i++)
{
freq[s[i]]++;
}
//先创建叶子
//将没找到暂时父亲的节点放在优先队列里,将(节点的,不单单只叶子)频次大小设定为优先级
priority_queue<HuffmanNode*,vector<HuffmanNode*>,Compare> pq;
for(auto it:freq)
{
// HuffmanNode *p=new HuffmanNode(it.first,it.second);
pq.push(new HuffmanNode(it.first,it.second));
}
//开始,合并最小的两个,出队,合并结果入队的循环操作,直到队里只剩下一个。
while(pq.size()>1)
{
//最小值为左儿子,次小值为右儿子
HuffmanNode* left=pq.top();
pq.pop();
HuffmanNode* right=pq.top();
pq.pop();
HuffmanNode* parent=new HuffmanNode('#',left->freq+right->freq);
parent->left=left;
parent->right=right;
pq.push(parent);
}
root=pq.top();//最后剩下根
getcodes(root,"");
}
//求出每个叶子对应的01编码
void getcodes(HuffmanNode* root,string s)
{
if(root==NULL)
{
return ;
}
if(root->date!='#')
{
codes[root->date]=s;
}
//往左走
getcodes(root->left,s+'0');
getcodes(root->right,s+'1');
}
步骤二、求出每个叶子的01编码
在步骤一里
步骤三、利用已经键好的哈夫曼树,对输入串解码或对01串译码
//刚开始输入串对应的哈夫曼编码
string getEncodedText(string s)
{
string Encodedtext="";
for(auto i:s)
{
Encodedtext+=codes[i];
}
return Encodedtext;
}
//构建好哈夫曼树后,任意输入一个有效串(串中每个字符在哈夫曼树的叶子中能找到),输出01编码
string str_to_01(string s)
{
string codeed="";
for(int i=0;i<s.length();i++)
{
if(freq.find(s[i])==freq.end())
{
cout<<"字符"<<s[i]<<"无法编码!!!!!!\n";
return "######";
}
codeed+=codes[s[i]];
}
return codeed;
}
//输入01编码,输出解码后的串(解码)
string _01_to_str(string encode)
{
string ans="";
HuffmanNode *cur=root;
for(auto i:encode)
{
if(i=='0')
cur=cur->left;
else
cur=cur->right;
if(cur==NULL)
{
cout<<"你输入的01编码无法翻译!!!!!\n";
return "######";
}
if(cur->date!='#')
{
ans+=cur->date;
cur=root;
}
}
if(cur!=root) cout<<"所给的01编码不完整,但是能翻译。\n";
return ans;
}
完整代码:
#include<iostream>
#include<map>
#include<queue>
#include<vector>
#include<string>
using namespace std;
//节点类
class HuffmanNode{
public:
char date;
int freq;
HuffmanNode *left;
HuffmanNode *right;
HuffmanNode(char date,int freq)
{
this->date=date;
this->freq=freq;
left=right=NULL;
}
};
//确定优先队列的优先级
class Compare{
public:
bool operator()(HuffmanNode *a,HuffmanNode *b)
{
return a->freq > b->freq;
}
};
class Huffman{
public:
Huffman(string s)
{//用构造函数来构造一颗哈夫曼数
//创建 字符---出现次数 的键值对
for(int i=0;i<s.size();i++)
{
freq[s[i]]++;
}
//先创建叶子
//将没找到暂时父亲的节点放在优先队列里,将(节点的,不单单只叶子)频次大小设定为优先级
priority_queue<HuffmanNode*,vector<HuffmanNode*>,Compare> pq;
for(auto it:freq)
{
// HuffmanNode *p=new HuffmanNode(it.first,it.second);
pq.push(new HuffmanNode(it.first,it.second));
}
//开始,合并最小的两个,出队,合并结果入队的循环操作,直到队里只剩下一个。
while(pq.size()>1)
{
//最小值为左儿子,次小值为右儿子
HuffmanNode* left=pq.top();
pq.pop();
HuffmanNode* right=pq.top();
pq.pop();
HuffmanNode* parent=new HuffmanNode('#',left->freq+right->freq);
parent->left=left;
parent->right=right;
pq.push(parent);
}
root=pq.top();//最后剩下根
getcodes(root,"");
}
//求出每个叶子对应的01编码
void getcodes(HuffmanNode* root,string s)
{
if(root==NULL)
{
return ;
}
if(root->date!='#')
{
codes[root->date]=s;
}
//往左走
getcodes(root->left,s+'0');
getcodes(root->right,s+'1');
}
//刚开始输入串对应的哈夫曼编码
string getEncodedText(string s)
{
string Encodedtext="";
for(auto i:s)
{
Encodedtext+=codes[i];
}
return Encodedtext;
}
//构建好哈夫曼树后,任意输入一个有效串(串中每个字符在哈夫曼树的叶子中能找到),输出01编码
string str_to_01(string s)
{
string codeed="";
for(int i=0;i<s.length();i++)
{
if(freq.find(s[i])==freq.end())
{
cout<<"字符"<<s[i]<<"无法编码!!!!!!\n";
return "######";
}
codeed+=codes[s[i]];
}
return codeed;
}
//输入01编码,输出解码后的串(解码)
string _01_to_str(string encode)
{
string ans="";
HuffmanNode *cur=root;
for(auto i:encode)
{
if(i=='0')
cur=cur->left;
else
cur=cur->right;
if(cur==NULL)
{
cout<<"你输入的01编码无法翻译!!!!!\n";
return "######";
}
if(cur->date!='#')
{
ans+=cur->date;
cur=root;
}
}
if(cur!=root) cout<<"所给的01编码不完整,但是能翻译。\n";
return ans;
}
void showfreq()
{
cout<<"字符--对应频率\n";
for(auto i:freq)
{
cout<<i.first<<" "<<i.second<<endl;
}
}
void showcodes()
{
cout<<"字符--对应编码\n";
for(auto i:codes)
cout<<i.first<<" "<<i.second<<endl;
}
private:
map<char,int> freq; //字符--对应频次
map<char,string> codes; //字符--对应编码
HuffmanNode *root;
};
int main()
{
string s="casbcatbsatbat";
string _01code="1101000";
Huffman h(s);
cout<<s<<endl;
h.showfreq();
h.showcodes();
cout<<"对原字符串{"<<s<<"}编码:";
string encodedText = h.getEncodedText(s);
cout<<encodedText<<endl;
cout<<"对{"<<_01code<<"}解码:";
string decoded = h._01_to_str(_01code);
cout<<decoded<<endl;
cout<<"对"<<"{abcstdd}编码\n";
cout<<h.str_to_01("abcstdd")<<endl;
return 0;
}