哈夫曼树
基本概念
1.路径:从树中一个结点到另一个结点之间的分支构成这两个结点间的路径。
2.结点的路径长度:两结点路径上的分支数。
3.树的路径长度:从树根到每个结点的路径长度之和。
如图:从A到B,C,D,E,F,G,H,I的路径长度分别为1,1,2,2,2,2,3,3。树的路径长度为16。
注意:结点数目相同的二叉树中,完全二叉树是路径最短的二叉树,但路径最短的二叉树不一定是完全二叉树,因为叶子结点可以移动到其他位置。(充分条件)
4.权:将树中结点赋给一个有某种含义的数值,则这个数值称为该结点的权。
5.结点的带权路径长度:从根结点到该结点之间的路径长度与该结点权的乘积。
6.树的带权路径长度:树中所有叶子结点的带权路径长度之和。
哈夫曼树定义:最优(二叉)树,带权路径长度最短的(二叉)树。
哈夫曼树构造算法
算法解释
哈夫曼树中权越大的叶子离根越近贪心算法:构造哈夫曼树时首先选择权值小的叶子结点
(1)根据n个给定的权值{W1,W2,...Wn,}构成n棵二叉树的森林F={T1,T2,...,Tn},其中Ti只有一个带权Wi的根结点。
(2)在F中选取两棵根结点的权值最小的树作为左右子树,构造一棵新的二叉树,且设置新的二叉树的根结点的权值为左右子树根结点的权值之和。
(3)在F中删除选取的两个树,并将新得到的二叉树加入森林。
哈夫曼树的结点的度数都为0,2,没有度为1的结点。
包含n个叶子结点的哈夫曼树共有2n-1个结点。
算法表示
采用顺序存储结构——一维结构数组:
typedef struct {
int wight;
int parent, lch, rch;
}HTNode,*HTLink;
构造哈夫曼树:
创建一个大小为2n-1的一维数组,前n(1~n)个储存根结点,后面n-1个用来存储新的根结点。从所有根节点中,选两个最小的根节点,构成新的二叉树,以此递归。
#include<stdio.h>
#include<stdlib.h>
typedef struct {
int wight;
int parent, lch, rch;
}HTNode,*HTLink;
void SelectNode(HTLink HT, int n, int& s1,int &s2){
int x1 = 0, x2 = 0;
int w1 = 999, w2 = 999;
for (int i = 1; i <= n; i++) {
if (HT[i].parent == 0 && HT[i].wight < w1) {
w2 = w1;
x2 = x1;
w1 = HT[i].wight;
x1 = i;
}
else if (HT[i].parent == 0 && HT[i].wight < w2)
{
w2 = HT[i].wight;
x2 = i;
}
s1 = x1;
s2 = x2;
}
}
void CreatHTLink(HTLink& HT, int n) {
HT = (HTNode*)malloc((2 * n) * sizeof(HTNode));
int s1, s2;
for (int i = 1; i < 2 * n; i++) {
HT[i].parent = 0;
HT[i].lch = 0;
HT[i].rch = 0;
}
for (int i = 1; i <= n; i++) {
scanf_s("%d", &HT[i].wight);
}
for (int i = n+1; i <= 2 * n - 1; i++) {
SelectNode(HT, i-1, s1, s2);
HT[i].lch = s1;
HT[i].rch = s2;
HT[i].wight = HT[s1].wight + HT[s2].wight;
HT[s2].parent = i;
HT[s1].parent = i;
}
return;
}
哈夫曼树应用
哈夫曼编码
(1)统计字符集中每个字符出现的平均概率(概率越大,要求编码越短);
(2)利用哈夫曼树的特点:权越大的叶子离根节点越近,将每个字符的概率值作为权值,构建哈夫曼树。概率越大,路径越短;
(3)在哈夫曼树的每个分支上标上0或1,结点的左分支标0,右分支标1。把从根结点到每个叶子结点的路径的标号连起来,作为该叶子代表的字符编码。
为什么哈夫曼编码能够保证是前缀编码?
因为在哈夫曼树中,没有一片树叶是另一片树叶的祖先,所以每个叶结点的编码不可能是其它叶结点的编码前缀。
为什么哈夫曼编码能够保证字符编码总长最短?
因为哈夫曼树带权路径最短,故字符编码总长最短。
性质1:哈夫曼编码是前缀码。
性质2:哈夫曼编码是最优前缀码。
算法实现:从叶子递归到根结点编码,再翻过来就是哈夫曼编码,创建二维字符串数组