也因如此,它作为博主大二上学期最重要的必修课出现了。由于大家对于上学期C++系列博文的支持,我打算将这门课的笔记也写作系列博文,既用于整理、消化,也用于同各位交流、展示数据结构的美。
此系列文章,将会分成两条主线,一条“数据结构基础”,一条“数据结构拓展”。“数据结构基础”主要以记录课上内容为主,“拓展”则是以课上内容为基础的更加高深的数据结构或相关应用知识。
每一个传奇的数据结构都会有属于自己的传奇故事
——尤市沃茨基硕的
哈夫曼树的传奇背景,是主角哈夫曼在攻读博士学位期间,修习信息论学科。导师让同学们选择学期考察的方案,可以选择完成报告,或者参加考试。我们的dalao哈夫曼就选择了完成一篇报告。
他遇到的报告题目为:寻找最有效的二进制编码方案
他先是分析了前人的研究结果,发现前辈们的方案都并未很好的解决这个问题,尤其是不能证明其方案是最有效的。水不成论文的哈夫曼此时的心中一定是:

于是他决定先放弃对已有算法的分析,自己研究一个新的算法解决编码问题:

最终,他发明了一个基于有序频率二叉树的编码方案,并很快证明了他是最有效的算法。这个算法,青出于蓝,超过了信息创始人香农和他的导师。哈夫曼使用自底向上的方法构建二叉树,避免了次优算法Shannon-Fano编码的最大弊端──自顶向下构建树。
1952年,哈夫曼将这个发明整理成了学期报告《一种构建极小多余编码的方法》(A Method for the Construction of Minimum-Redundancy Codes)一文,顺利的完成了该科目的学习~~(这要是不过那可真说不过去了)~~ 。现在这种编码方案一般就叫做哈夫曼(Huffman)编码。

编码方案在上文描述算法的时候我们有提到过。要获取所有的字符编码,我们需要遍历整颗哈夫曼树,对于所有存放字符的叶子结点,应该获取并记录其编码。
那么首先,我们应该有一个存放编码的地方:
char * code[26];
接着,来遍历这棵树。当然,遍历整棵树获取编码的方式有很多,这里博主仅提供一种可供参考的方案:
void encode(char * s,int dep,Node * node){
if(node->left == NULL && node->right == NULL){
int c = node->c - 'a';
s[dep] = '\0';
code[c] = strdup(s);
return;
}
s[dep] = '0';
encode(s,dep + 1,node->left);
s[dep] = '1';
encode(s,dep + 1,node->right);
}
完成编码之后,在主函数中调用一下:
Node * root;
...
char s[20];
encode(s,0,root);
for(int i = 0;i < 26;i++){
if(code[i] != NULL){
cout << (char)(i + 'a') << ":" << code[i] << endl;
}
}

一般来说,译码工作是在哈夫曼树构建成功基础上,给定一个编码序列,将其翻译成为源码,如果无法翻译则给予提示。
例如,对于刚才展示的运行示例,如果给定编码序列为:
110110101
110110101
110110101
则译码序列为:
a
a
b
aab
aab
若为:
11011010
11011010
11011010,则这个序列存在问题,因为没有字符编码为10
译码的过程本质上是在哈夫曼树上跟着序列进行模拟
- 如果当前结点对应位为0,则下一位跳转到左结点
- 如果当前结点对应位为1,则下一位跳转到右结点
- 如果当前结点为叶子结点,则获得一位译码。当前结点指向根节点,重复上述过程
- 如果当前结点对应最后一位编码且非叶子结点,说明编码存在问题。
用代码实现以下:
char * decode(Node * root,char * target){
char result[50];
int resultCnt = 0;
int targetCnt = strlen(target);
Node * curr = root;
for(int i = 0;i < targetCnt;i++){
if(target[i] == '0'){
curr = curr->left;
}else{
curr = curr->right;
}
if(curr->left == NULL && curr->right == NULL){
result[resultCnt++] = curr->c;
curr = root;
}
}
if(curr != root){
return "INVALID";
}else{
result[resultCnt] = '\0';
return strdup(result);
}
}
在主函数中调用测试这个函数:
Node * root;
char str[100];
...
while(true){
cout << "请输入原码:";
cin >> str;
cout << "译码结果为:" << decode(root,str) << endl;
}
跑几组数据试一试:

ok没有问题~

完整的堆实现的代码这里再放一下:
#include<iostream>
#include<cstring>
using namespace std;
class Node{
public:
char c;
int value;
int idx;
Node * left;
Node * right;
bool operator < (const Node & node){
if(value == node.value){
return idx < node.idx;
}
return value < node.value;
}
};
Node * character[26];
Node * heap[2000];
int tot = 0;
void down(int k){
int son = k << 1;
while(son <= tot){
if(son + 1 <= tot && *heap[son + 1] < *heap[son]){
son++;
}
if(*heap[k] < *heap[son]){
break;
}
Node * temp = heap[k];
heap[k] = heap[son];
heap[son] = temp;
k = son;
son <<= 1;
}
}
void up(int k){
int fa = k >> 1;
while(fa != 0){
if(*heap[fa] < *heap[k]){
break;
}
Node * temp = heap[k];
heap[k] = heap[fa];
heap[fa] = temp;
k = fa;
fa >>= 1;
}
}
void add(Node * node){
heap[++tot] = node;
up(tot);
}
Node * peak(){
return heap[1];
}
void pop(){
heap[1] = heap[tot--];
down(1);
}
char * code[26];
void encode(char * s,int dep,Node * node){
if(node->left == NULL && node->right == NULL){
int c = node->c - 'a';
s[dep] = '\0';
code[c] = strdup(s);
return;
}
s[dep] = '0';
encode(s,dep + 1,node->left);
s[dep] = '1';
encode(s,dep + 1,node->right);
}
char * decode(Node * root,char * target){
char result[50];
int resultCnt = 0;
int targetCnt = strlen(target);
Node * curr = root;
for(int i = 0;i < targetCnt;i++){
if(target[i] == '0'){
curr = curr->left;
}else{
curr = curr->right;
}
if(curr->left == NULL && curr->right == NULL){
result[resultCnt++] = curr->c;
curr = root;
}
}
if(curr != root){
return "INVALID";
}else{
result[resultCnt] = '\0';
return strdup(result);
}
}
char str[10000];
int main(){
cin >> str;
int len = strlen(str);
for(int i = 0,c;i < len;i++){
c = str[i] - 'a';
if(character[c] == NULL){
character[c] = new Node();
character[c]->c = str[i];
character[c]->idx = i;
character[c]->value = 1;
character[c]->left = NULL;
character[c]->right = NULL;
add(character[c]);
}else{
character[c]->value++;
}
}
int cnt = tot;
Node * first;
Node * second;
Node * node;
while(tot > 1){
first = peak();
pop();
second = peak();
pop();
node = new Node();
node->c = 'z' + 1;
node->value = first->value + second->value;
node->left = first;
node->right = second;
node->idx = len++;
add(node);
}
Node * root = peak();
char s[20];
encode(s,0,root);
for(int i = 0;i < 26;i++){
if(code[i] != NULL){
cout << (char)(i + 'a') << ":" << code[i] << endl;
}
}
while(true){
cout << "请输入原码:";
cin >> str;
cout << "译码结果为:" << decode(root,str) << endl;
}
}