数据结构:赫夫曼树的编码和译码过程
- 写在前面的话
这篇文章是根据严蔚敏版的数据结构中6.6.2赫夫曼编码中的部分代码所写,主要是记录一下自己对这些代码的理解,并且在那本书中采用了类c语言写的,在计算机上无法直接实现,我便根据自己的理解对代码进行适当的改写,使其在计算机上可以运行。
(此外,吐槽一句,书上用所谓的类c语言写,确实节省了书的空间,老师讲的时候也更方便,但是自己去实现的时候是真的令人头秃。。。。。)
- 学前小知识
路径长度:从树的一个结点到另一个结点之间的分支构成这两个节点之间的路径,而路径上的分支数目便称作路径长度。从树根到每一个结点的路径长度之和称为树的路径长度。
树的带全路径长度:结点的带全路径长度为该从结点到树根之间的路径长度与结点上的权的乘积,而树中所有叶子结点的带全路径长度之和叫做熟的带全路径长度。
赫夫曼树:又称最优二叉树,是指带全路径长度WPL最小的二叉树。
前缀编码:若要设计长短不等的编码,则必须是任一个字符的编码都不是另一个字符编码的前缀。而赫夫曼编码便是总长度最短的前缀编码。
- 赫夫曼树的编码
当给到我们一组有权重的数时:
weight | parent | lchild | rchild | |
---|---|---|---|---|
1 | 5 | 0 | 0 | 0 |
2 | 29 | 0 | 0 | 0 |
3 | 7 | 0 | 0 | 0 |
4 | 8 | 0 | 0 | 0 |
5 | 14 | 0 | 0 | 0 |
6 | 23 | 0 | 0 | 0 |
7 | 3 | 0 | 0 | 0 |
8 | 11 | 0 | 0 | 0 |
9 | - | 0 | 0 | 0 |
10 | - | 0 | 0 | 0 |
11 | - | 0 | 0 | 0 |
12 | - | 0 | 0 | 0 |
13 | - | 0 | 0 | 0 |
14 | - | 0 | 0 | 0 |
15 | - | 0 | 0 | 0 |
( a)HT的初态
首先便是将其构成赫夫曼树。其方法如图所示:
由方法可将上面的数组填充完整:
weight | parent | lchild | rchild | |
---|---|---|---|---|
1 | 5 | 9 | 0 | 0 |
2 | 29 | 14 | 0 | 0 |
3 | 7 | 10 | 0 | 0 |
4 | 8 | 10 | 0 | 0 |
5 | 14 | 12 | 0 | 0 |
6 | 23 | 13 | 0 | 0 |
7 | 3 | 9 | 0 | 0 |
8 | 11 | 11 | 0 | 0 |
9 | 8 | 11 | 1 | 7 |
10 | 15 | 12 | 3 | 4 |
11 | 19 | 13 | 8 | 9 |
12 | 29 | 14 | 5 | 10 |
13 | 42 | 15 | 6 | 11 |
14 | 58 | 15 | 2 | 12 |
15 | 100 | 0 | 13 | 14 |
(b)HT的终态
由定义可知,赫夫曼树中没有度为1的结点,则一棵有n个子叶结点的赫夫曼树共有2n-1个结点。
而根据图6.25可知若要求赫夫曼编码,则需从叶子结点出发走出一条从叶子到根的路径。所以在定义赫夫曼树和赫夫曼编码的存储结构时,不仅要考虑一个结点的权重,还需考虑该结点的双亲和孩子结点的位置。
所以其顺序存储结构可以这样定义
typedef struct {
unsigned int weight;
unsigned int parent,lchild,rchild;
}HTNode,*HuffmanTree; //动态分配数组存储赫夫曼树
typedef char * * HuffmanCode;//动态分配数组存储赫夫曼编码表
其中weight表示权重,parent表示双亲,lchild表示左孩子,rchild表示右孩子。
接下来便是赫夫曼的编码:
//---------------基本操作的算法描述----------------
void HuffmanCoding(HuffmanTree &HT,HuffmanCode &HC,int *w,int n){
//w存放n个字符的权值(均>0),构造赫夫曼树HT,并求出n个字符的赫夫曼编码HC
//------构造赫夫曼树----
{
if(n<=1){
return ERROR;
}
m = 2 * n - 1;
HT = (HuffmanTree)malloc((m+1)*sizeof(HTNode));//0号单元未用
for(int i = 1;i <= n;i++){
//构造(a)有数据部分
HT[i].weight = w[i];
HT[i].parent = HT[i].lchild = HT[i].rchild = 0;
}
for(int i = n+1;i <= m;i++){
//构造(a)没有有数据部分
HT[i].weight = HT[i].parent = HT[i].lchild = HT[i].rchild = 0;
}
for(int i = n+1;i <= m;i++){ //构建赫夫曼树
//在HT[1...i-1]选择parent为0且weight最小的两个节点,其序号分别为s1和s2.
//构造(b)
Select (HT,i-1,s1,s2);//自定义select函数,用于查找权重最小的两个结点
HT[s1].parent = i;
HT[s2].parent = i;
HT[i].lchild = s1;
HT[i].rchild = s2;
HT[i].weight = HT[s1].weight + HT[s2].weight;
}
}
//------从叶子到根逆向求每个字符的赫夫曼编码----
HC = (HuffmanCode)malloc((n+1)*sizeof(char*));//分配n个字符编码的头指针向量
char cd = (char*)malloc(n*sizeof(char)); //分配求编码的工作空间
cd[n-1] = "\0"; //编码结束符
for(int i = 1;i <= n;i++){ //逐个字符求赫夫曼编码
int start = n - 1; //编码结束符位置
for(char c = i,f = HT[i].parent;f != 0;c = f,f = HT[f].parent){
//从叶子到根逆向求编码
if(HT[f].lchild == c){
sd[--start] == "0";
} else{
sd[--start] == "1";
}
}
HC[i] = (char*)malloc((n-start)*sizeof(char));//为第i个字符编码分配空间
strcpy(HC[i],&sd[start]);//自定义strcpy函数,用于从cd复制编码(串)到HC
}
free(cd);//释放空间
}
至此,赫夫曼编码完成。
未完待续… … …