More Related Content More from Wang Yizhe (14) 第六章 树和二叉树21. 6.8 哈 夫 曼 树 与
哈 夫 曼 编 码
• 最优树 ( 哈夫曼树 ) 的定
义
• 如何构造最优树
• 前缀编码
• 哈夫曼编码
4. WPL(T)= WPL(T)= WPL(T)=
7×2+5×2+ 7×3+5×3+ 7×1+5×2+
2×2+4×2 4×2+2×1 2×3+4×3
=36 =46 =35
5. 二、如何构造最优树
( 哈夫曼算法 ) 以二叉树为例:
(1) 根据给定的 n 个权值 {w1, w2,
…, wn} ,构造 n 棵二叉树的集合
F = {T1, T2, … , Tn} ,
其中每棵二叉树 Ti 中均只含一个带
权值
为 wi 的根结点,其左、右子树为空
7. (3) 从 F 中删去这两棵树,同时加入
刚生成的新树;
(4) 重复 (2) 和 (3) 两步,直至
F 中只
含一棵树为止。这棵树便是哈夫
8. 例如 : 已知权值 W={ 5, 6, 2, 9, 7 }
5 6 2 9 7
6 9 7 7
5 2
9 7 13
5 2 6 7
9. 9 7 13
5 2 6 7
29
13 16
6 7 9 7
5 2
10. 注意
:
• ① 初始森林中的 n 棵二叉树,
每棵树有一个孤立的结点,它们既
是根,又是叶子
② n 个叶子的哈夫曼树要经过
n- 1 次合并,产生 n- 1 个新结点。
最终求得的哈夫曼树中共有 2n- 1
个结点。
③ 哈夫曼树是严格的二叉树,
11. 前缀编码
在电文传输中,需要将电文中出现的
每个字符进行二进制编码。在设计编码时
需要遵守两个原则:
( 1 )发送方传输的二进制编码,到接收方
解码后必须具有唯一性,即解码结果与发
送方发送的电文完全一样;
( 2 )发送的二进制编码尽可能地短。下面
我们介绍两种编码的方式。
12. 1. 等长编码
这种编码方式的特点 :
每个字符的编码长度相同。
设字符集只含有 4 个字符
A,B,C ,D,
用两位二进制表示的编码分别为
00 , 01 , 10 ,
11 。若现在电文为: A B A C C D A ,
则应发送二进制序列:
00010010101100 ,
总长度为 14 位。当接收方接收到这段电
文后,
将按两位一段进行译码。
13. 2. 不等长编码
在传送电文时,为了使其二
进制位数尽
可能地少,可以将每个字符的编码设计为不等
长
的,使用频度较高的字符分配一个相对比较短
的
编码,使用频度较低的字符分配一个比较长的
编
码。例如,可以为 A , B , C , D 四个字符
分别分配
0 , 00 , 1 , 01 ,并可将上述电文用二进制
序列:
15. 哈夫曼编码的构造方法 :
( 1 )利用字符集中每个字符的使
用
频率作为权值构造一个哈夫曼树;
( 2 )从根结点开始,为到每个叶
子
结点路径上的左分支赋予 0 ,右分
支
16. 例如:
假设有一个电文字符集中有 8 个字符,每
个字符
的使用频率分别为
{0.05,0.29,0.07,0.08,0.14,0.23,
0.03,0.11} ,现以此为例设计哈夫曼编码。
为方便计算,将所有字符的频度乘以
100 ,使
其转换成整型数值集合,得到
{ 5, 29, 7, 8, 14, 23,
17. 100
0 1
42 58
0 1 0 1
19 29
00 0 1 10 0 1
8 15
010 0 1 110 0 1
0110 0111 1110 1111
5 29 7 8 14 23 3 11
18. 哈夫曼树的存储结
构
用一个大小为 2n- 1 的向量来存储哈夫曼
树中
的结点,其存储结构为:
typedef struct { // 结点类型
int weight ; // 权值,不妨设权值
均大于零
int lchild , rchild , parent ;
// 左右孩子及双亲指针
哈夫曼编码的存储结构
}HTNode , *HuffmanTree;
19. HT 数组
求哈夫曼编码过程的实例 : Weight parent lchild rchild
0 1 2 3 4 5 6 7 1
5 9
0 0 0
W 5 29 7 8 14 23 3 11 2
29 14
0 0 0
0 1 2 3 4 5 6 7 3 7 10
0 0 0
cd 0 1 1 0 0 4 8 10
0 0 0
↑ 5 14 12
0 0 0
start 6 23 0
13 0 0
HC 7 3 9
0 0 0
1 0 1 1 0
2 1 0
8 11 11
0 0 0
9 8 0
11 1
0 7
0
3 1 1 1 0
10 15 12
0 3
0 4
0
4 1 1 1 1 19 13
0 8
0 9
0
11
5 1 1 0 29 0
14 0
5 10
0
12
6 0 0 42 15
0 6
0 0
11
7 0 1 1 1 13
14 58 0
15 2
0 12
0
8 0 1 0 100 0 13
0 14
0
15
20. 求每个字符的 Huffma n 编码是从叶子到根
逆向处理 , 即从叶子出发 , 沿着双亲线索 , 回
溯到根节点 , 求得各个叶子节点所表示的字符
的编码 .
HT(‘5’).parent = ‘8’
0 HT(‘8’).lchild = ‘5’
HT(‘8’).parent = ‘19’
1 HT(‘19’).rchild = ‘8’
HT(‘19’).parent = ‘42’
1
HT(‘42’).rchild = ‘19’
0 HT(‘42’).parent = ‘100’
HT(‘100’).ichild = ‘42’
HT(‘100’).parent = 0
‘5’: 0110
21. 求哈夫曼编码的算法 6.12:
void HuffmanCoding(HuffmanTree &HT,
HuffmanCode &HC, int *w,int n){
if(n<=1) return;
m=2*n-1;
HT=(HuffmanTree)malloc((m+1)*sizeof(HTNode));
for(p=HT+1,i=1;i<=n;++i,++p,++w) *p={*w,0,0,0}
for(;i<=m;++i,++p) *p={0,0,0,0}
for(i=n+1 ; i<=m ; i++){
SelectMin(HT , i-1 , s1 , s2) ;
HT[s1].parent=i; HT[s2].parent=i ;
HTIi].1child=s1 ;
HT[i].rchild=s2 ;
HT[i].weight=T[s1].weight+T[s2].weight ;
} // end for 创建哈夫曼树
23. // 从叶子到根逆向求每个字符的赫夫曼编码
for(i=1;i<=n;i++){
start=n-1 ;
for( c=i,f=HT[i].parent; f!=0;
c=f,f=HT[f].parent)
//i 为当前的树叶 , f 为 i 的双亲 . 每次循环沿
着双亲信息逆向搜寻 , 直至根节点
HC[i]=(char * )malloc((n-start)*sizeof(char));
if(HT[f].lchild==c cd[--start]=“0”;
strcpy(Hc[i] , &cd[start]) ;
else cd[--start]=“1”;
}
24. 求每个字符的 Huffma n 编码是从叶子到根
逆向处理 , 也可以从根出发 , 遍历整棵树 , 求得
各个叶子节点所表示的字符的编码 .
HT(‘100’).lchild =’42’
Cd[0] = “0”
0 HT(‘42’).lchild =’23’
Cd[1] = “0”
0 HT(‘23’).lchild =0
HT(‘23’).rchild =0
‘23’: 00
25. // 无栈非递归遍历 Haffman 树 , 求 Haffman 编码
...
HC=(HuffmanCode)malloc(n+1)*sizeof(char *));
p = m; cdlen = 0; // 指向根节点
for (i = 1; i<=m; ++i)
HT[i].weight = 0; // 遍历时用作节点的状态标
志
while(p) {
... // 从根开始遍历整棵树 , 求取每个节点的编码
}
26. // 无栈非递归遍历 Haffman 树 , 求 Haffman 编码
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 ] = ( c ha r
* ) ma llo c ( ( c d le n+1) * s iz e o f( c ha r) ) ;
c d [ c d le n] = “ 0” ;
s trc p y( HC [ p ] , c d ) ;
} //end else if
}
27. // 无栈非递归遍历 Haffman 树 , 求 Haffman 编码
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 = 0;
p = HT[p].parent;
--cdlen; // 退到父亲节点 , 编码长度减 1
} //end else
28. // HT[p].weight = 2 时 ,
HT[p].weight = 0; HT(‘23’).lchild =0
p = HT[p].parent; HT(‘23’).rchild =0
--cdlen; // 退到父亲节点 , 编码长度减 1
cd: 00
0 p = HT[’23’].parent =
’42’
0 cdlen = 2-1 = 2
cd: 0
再开始遍历 ’ 42’ 的
右边
...