哈夫曼树(Huffman Tree)是一种基于字符频率优化的数据压缩算法。其数据结构仍然是二叉树
用10,2,3,6,8五个权值作为叶子结点,构造二叉树,可能有多种不同形态。
如何能使得所有叶子结点的带权路径长度之和最小?
带权路径长度=结点的权值 *从根结点到该节点的路径长度。
一棵二叉树,每个叶子结点赋予权值
WPL=(2+3)*3+(10+6+8)*2
n个叶子结点构成的哈夫曼树,总结点个数为2n-1
具体代码如下:
定义存储结构
#include<stdio.h>
#include<stdlib.h>
typedef struct node{
float weight;
int parent,lChild,rChild;
}haffmanTree;
创建哈夫曼树
haffmanTree *creaHaffmanTree(int n){
haffmanTree *ht;
ht=(haffmanTree *)malloc(sizeof(haffmanTree)*(2*n-1));
int k=0,i,i1,i2;
for(i=0;i<2*n-1;++i) ht[i].lChild=ht[i].rChild=ht[i].parent=-1;
printf("依次输入每个叶子结点的权值:\n");
while(k<n) {
scanf("%f",&ht[k].weight);
++k;
}
while(k<2*n-1){
i1=0;
while(i1<k){
if(ht[i1].parent==-1) break;//此时已经到了根结点
i1++;
}
i=i1+1;
while(i<k){
if(ht[i].parent==-1 &&ht[i].weight<ht[i1].weight)
i1=i;
i++;
}
i2=0;
while(i2<k){
if(ht[i2].parent==-1&&i2!=i1) break;
i2++;
}
i=i2+1;
while(i<k){
if(ht[i].parent==-1 &&i!=i1&&ht[i].weight<ht[i2].weight)
i2=i;
i++;
}
ht[k].weight=ht[i1].weight+ht[i2].weight;
ht[k].lChild=i1;
ht[k].rChild=i2;
ht[i2].parent=ht[i1].parent=k;
++k;
}
return ht;
}
freeHaffmanTree(haffmanTree *ht){
free(ht);
}
这里的这个创建函数的核心逻辑是 while(k<2*n-1)之后的几个循环代码,也是是最不好理解的,我们细细拆开分析
while(k < 2 * n - 1){
// ...
}
这个循环会一直执行,直到k
等于2 * n - 1
。变量k
用于跟踪当前正在处理的节点索引。由于有n
个叶子节点和n-1
个内部节点,总共有2n - 1
个节点。
i1 = 0;
while(i1 < k){
if(ht[i1].parent == -1) break;
i1++;
}
这段代码使用一个内层循环找到第一个未被分配父节点(即parent == -1
)的节点,这个节点是当前最小权重的节点之一。前面代码中 for(i=0;i<2*n-1;++i) ht[i].lChild=ht[i].rChild=ht[i].parent=-1;将每个结点都置为-1,表示该节点尚未被连接到树中。
i = i1 + 1;
while(i < k){
if(ht[i].parent == -1 && ht[i].weight < ht[i1].weight)
i1 = i;
i++;
}
在第二个内层循环中,代码继续寻找第二个最小权重的节点。如果找到一个未分配父节点且权重小于当前找到的最小节点i1
的节点,就更新i1
。
i2 = 0;
while(i2 < k){
if(ht[i2].parent == -1 && i2 != i1) break;
i2++;
}
这段代码使用另一个内层循环找到第三个未被分配父节点的节点,这个节点是当前第二小权重的节点。
i = i2 + 1;
while(i < k){
if(ht[i].parent == -1 && i != i1 && ht[i].weight < ht[i2].weight)
i2 = i;
i++;
}
在第二个内层循环中,代码继续寻找第二小权重的节点。如果找到一个未分配父节点、索引不等于i1
且权重小于当前找到的第二小节点i2
的节点,就更新i2
。
ht[k].weight = ht[i1].weight + ht[i2].weight;
ht[k].lChild = i1;
ht[k].rChild = i2;
ht[i2].parent = ht[i1].parent = k;
一旦找到两个最小权重的节点(i1
和i2
),就创建一个新的内部节点(索引为k
)。新节点的权重是两个最小节点权重的和。新节点的左右子节点分别设置为i1
和i2
,并且更新i1
和i2
的父节点为k
。
++k;
最后,更新k
的值,准备下一轮循环,寻找下一对最小权重节点。
打印哈夫曼树
void printHaffmanTree(haffmanTree *ht,int n){
printf("数组下标 结点权值 双亲下标 左孩子下标 右孩子下标\n");
int i;
for(i=0;i<2*n-1;++i){
printf("%-10d%-10f%-10d%-12d%-12d\n",i,ht[i].weight,ht[i].parent,ht[i].lChild,ht[i].rChild);
}
}
调用主函数运行
int main(){
int n;
printf("请输入叶子结点个数:");
scanf("%d",&n);
haffmanTree *ht=creaHaffmanTree(n);
printHaffmanTree(ht,n);
freeHaffmanTree(ht);
return 0;
}
运行结果
具体哈夫曼树图如图: