路径:树中一个结点到另一个结点之间的分支所构成的路径
路径长度:两个结点之间的分支数
树的路径长度:从根结点到每个结点的路径长度之和,记作TL
结点数目相同的二叉树中,完全二叉树是路径长度最短的
结点的带权路径长度:根到该结点之间路径长度 X 该结点的权
树的带权路径长度:所有叶子结点的带权路径长度之和
记作WPL=
哈夫曼树:最优二叉树(带权路径长度(WPL)最短的树)
满二叉树不一定是哈夫曼树
哈夫曼树中权越大的叶子离根越近(贪心算法)
哈夫曼算法
1.根据n个给定的权值{w1,w2,...2n}构成n棵二叉树森林F={T1,T2,...,Tn},其中Ti只有一个带权为wi的根结点。(构造森林全是根)
2.从F中选择两个权值最小的作为左右子树来构造一颗新的二叉树(新二叉树根结点的权值为左右子树根结点之和)(选用两小造新树)
3.删除原来的两颗树,将新树加入森林(删除两小添新人)
4.重复步骤2-3,直到森林中仅剩一棵树(重复2、3剩单根)
例1:a,b,c,d权值分别为7,5,2,4
哈夫曼树只有度为0或2的结点,没有度为1的结点
包含n个叶子结点的哈夫曼树共有2n-1个结点
n棵二叉树要经历n-1次合并可以形成哈夫曼树
例2:a,b,c,d,e权值分别为7,5,5,2,4
算法实现
使用顺序存储结构实现--结构数组
weight(权重),parent(父母),lch,rch(左右孩子)
哈夫曼树中共2n-1个结点,不使用0为下标,需要数组大小2n
1.初始化HT[1...2n-1]:lch=rch=parent=0
2.输入初始n个叶子结点:设置HT[1...n]的weight值
3.进行n-1次合并
a.从HT[1...i-1]中选取连个未选择的(parent=0)weight最小的两个结点
b.修改HT[S1],HT[S2]的值,HT[S1].parent=HT[S2].parent=i
c.修改新产生的HT[i] HT[i].weight=HT[S1].weght+HT[S2].weight;
HT[i].lch=S1; HT[i].ch=s2;
void CreateHuffmanTree(HuffmanTree HT,int n){
if(n<=1) return;
m = 2*n-1;//数组共2n-1个元素
HT=new HTNode[m+1]; //0单号未用,HT[m]表示根结点
for(i=1;i<=m;++i){
HT[i].lch=0; HT[i].rch=0; HT[i].parent=0;
}
for(i=1;i<=n;++i) cin>>HT[i].weight;//输入前n个weight值
///*********初始化结束***********///
for(i=n+1;i<=m;i++){
Select(HT,s1,s2);//在HT中选择连个双亲域为0且权值最小的结点
HT[s1].parent=i; HT[s2].parent=i;//删除s1,s2
HT[i].lch=s1; HT[i].rch=s2; //s1,s2作为左右孩子
HT[i].weight=HT[s1].weight+HT[s2].weight;//设置新权值
}
}
应用:哈夫曼编码
不定长二进制编码,出现次数多的尽可能短(节约空间)!!!任意一个字符的编码不能是另一个字符编码的前缀--前缀编码
哈夫曼树: 1.统计字符在电文中出现的平均概率
2.根据平均概率构造哈夫曼树
3.结点左分支标0,右分支标1;把从根到每个叶子结点的路径上的标号连接起来,作为该叶子代表的字符编码
哈夫曼编码中每个字符(叶子结点)的编码(路径)都不会经过其他字符,所以不存在某个字符的编码是其他字符的前缀
哈夫曼编码的实现
1.查找该字符的双亲结点,并确定其是左孩子还是右孩子。
2.在临时存储区域中标注当前结点的状态,如果为左孩子标0,右孩子标1
3.将临时空间向前移动一位寻找下一个双亲结点,直到找到根结点为止
例
以第三个字符C为例
start | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
Loc | 13 | 12 | 11 | 3 | |||
parentLoc | 0 | 13 | 12 | 11 | |||
childTag | root | 1 | 0 | 1 | \0 |
Loc为结点自己的位置,parentLoc为双亲结点的位置,childTag为该结点是左孩子还是右孩子,讲childTag连接起来就是该字符的哈夫曼编码,\0为编码结束的标志
void CreateHuffmanCode(HuffmanTree HT,HuffmanCode &HC ,int n){
HC = new char*[n+1];//分配n个字符编码的头指针矢量
cd = new char[n];//分配临时存放编码的动态数组
cd[n-1] = '\0' //编码结束符
for(i=1;i<=n;++i){
start=n-1; c=i; f=HT[i].parent;
while(f!=0){ //从叶子结点开始向上回溯,直到根结点
--start; //回溯一次start向前指一个位置
if(HT[f].lchild == c) cd[start] = '0';//结点c为f的左孩子,标0
else cd[start] = '1';//结点c为f的右孩子,标1
c=f; f=HT[f].parent; //向上回溯
} //求第i个字符的编码
HC[i]= new char[n-start];//为第i个字符的编码分配空间
strcopy(HC[i],$cd[start];)//将求得的编码从临时空间cd复制到hc中
}
delete cd;//释放临时空间
}
哈夫曼树的应用:
1.编码文件 ①输入各字符的权值
②构造哈夫曼树HT[i]
③进行哈夫曼编码HC[i]
④查HC[i],得到各字符的哈夫曼编码
2.解码文件 ①构造哈夫曼树
②一次读入二进制编码
③读入0,则走向左孩子;读入1,则走向右孩子
④一旦到达某叶子时,即可译出字符
⑤然后从根出发继续译码,直到结束
原文为uvuwxxxvywyuyyvxxyxxuxuvvyvxy