参考资料:《数据结构(C语言版)严蔚敏著》
版权说明:未经作者允许,禁止转载。如引用本文内容,需标明作者及出处。如本文侵犯了您的权益,请联系我删除并致歉。
文章说明:如文章中出现错误,请联系我更改。如您对文章的内容有任何疑问,也欢迎来与我讨论。
本文正在施工中...请稍等...
哈夫曼树,又称最优二叉树,是一类带权路径长度最短的树。
1、结点间的路径:从树中一个结点到另一个结点之间的分支构成这两个结点之间的路径。
2、路径长度:路径上的分支数目称为路径长度。
3、树的路径长度:从树根到每一个结点的路径长度之和。
4、结点的带权路径长度:从该结点到树根之间的路径长度与该结点上权的乘积。
5、树的带权路径长度:树中所有叶子结点的带权路径长度之和。
按如下步骤,构造一棵哈夫曼树:
1、根据给定的n个权值构成n棵二叉树的集合
,其中每棵二叉树
中只有一个带权为
的根结点,其左右子树均为空。
2、在F中选取两棵根结点的权值最小的树作为左右子树构造一棵新的二叉树,且置新的二叉树的根结点的权值为其左、右子树上的根结点的权值之和。
3、在F中删除这两棵树,同时将新得到的二叉树加入F中。
4、重复步骤2和3,直到F只含一棵树为止。这棵树便是哈夫曼树。
1、每个初始结点最终都成为叶子结点,并且权值越小的结点到根结点的路径长度越大。
2、构造过程中共新建了n-1个结点,因此哈夫曼树中结点总数为2n-1。
证明:初始集合中树的总数为n,每次从集合中拿出两棵树构造出一棵新的树放进集合中,在此过程只增加一个新结点,同时集合中树的总数减一。也就是说,集合中每减少一棵树,就会增加一个新结点。所以当集合中只剩下一棵树时,增加了n-1个新结点。
3、哈夫曼树中叶子结点个数为n,分支结点个数为n-1。
4、每次构造都选择两棵树作为新结点的孩子,因此哈夫曼树中不存在度为1的结点。
5、树的带权路径长度等于所有分支结点的权值和。
证明:由于在哈夫曼树中分支结点的权值等于其子树的所有叶子结点的权值之和,所以对于某个叶子结点来说,它到根结点的路径上所有的分支结点的权值中都包含了一份该叶子结点的权值,由带权路径长度的计算公式得到,这条路径上所有分支结点的权值和中包含了一份该叶子结点的带权路径长度。对于所有的叶子结点都是这样,所以树中所有分支结点的权值和就等于树的带权路径长度。
typedef struct{
unsigned int weight;
unsigned int parent, lchild, rchild;
}HTNode, *HuffmanTree;
typedef char** HuffmanCode;
typedef struct{
unsigned int weight;
unsigned int parent, lchild, rchild;
}HTNode, *HuffmanTree;
typedef char** HuffmanCode;
void HuffmanCoding(HuffmanTree &HT, HuffmanCode &HC, int *w, int n){
//w存放n个字符的均值(均>0),构造哈夫曼树HT,并求出n个字符的哈夫曼编码HC。
if(n<=1) return;
m=2*n-1; ////哈夫曼树结点总个数
HT=(HuffmanTree)malloc((m+1)*sizeof(HNode)); //哈夫曼树分配空间,0号单元未用
for(p=HT+1, i=1; i<=n; ++i, ++p, ++w) *p={ *w, 0, 0, 0}; //哈夫曼初始n个结点的初始化
for(; i<=m; ++i; ++p) *p={ 0, 0, 0, 0}; //新结点的初始化
for(i=n+1; i<=m; ++i){ //构建哈夫曼树
//在HT[1..i-1]选择parent为0且weight最小的两个结点,其序号分别为s1和s2.
Select(HT, i-1, s1, s2);
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个字符编码的头指针向量
cd=(char*)malloc(n*sizeof(char)); //分配求编码的工作空间
cd[n-1]='\0'; //编码结束符
for(i=1; i<=n; ++i){ //逐个字符求哈夫曼编码
start=n-1; //编码结束符位置
for(c=i, f=HT[i].parent; f!=0; c=f, f=HT[f].parent) //从叶子到根逆向求编码
if(HT[f].lchild==c) cd[--start]='0';
else cd[--start]='1';
HC[i]=(char*)malloc((n-start)*sizeof(char)); //为第i个字符编码分配空间
strcpy(HC[i], &cd[start]); //从cd复制编码串到HC
}
free(cd); //释放工作空间
}
HC=(HuffmanCode)malloc((n+1)*sizeof(char*));
p=m; cdlen=0;
for(i=1; i<=m; ++i) HT[i].weight=0; //遍历哈夫曼树时用作结点状态标志
while(p){
if(HT[p].weight==0){ //向左
HT[p].weight=1;
if(HT[p].lchild!=0){ p=HT[p].lchild; cd[cdlen++]='0'; }
else if(HT[p].rchild==0){ //登记叶子结点的字符的编码
HC[p]=(char*)malloc((cdlen+1)*sizeof(char));
cd[cdlen]='\0'; strcpy(HC[p], cd); //复制编码串
}
}
else if(HT[p].weight==1){ //向右
HT[p].weight=2;
if(HT[p].rchild!=0) { p=HT[p].rchild; cd[cdlen++]='1'; }
} else { //HT[p].weight==2,后退
HT[p].weight=0; p=HT[p].parent; --cdlen; //退到父节点,编码长度减1
}
}