200 likes | 265 Views
数据结构. (第十二讲). 绍兴文理学院. 计算机系计算机应用教研室. 第 6 章 树和二叉树( 5 ). 一、教学目的: 明确最优二叉树 ( 赫夫曼树 ) 的概念,掌握构造赫夫曼树和赫夫曼编码方法,掌握构造赫夫曼树和赫夫曼编码的算法。基本掌握对赫夫曼编码的译码的方法,算法设计训练。. 二、教学重点: 最优二叉树 ( 赫夫曼树 ) 的概念,构造赫夫曼树和赫夫曼编码方法,构造赫夫曼树和赫夫曼编码的算法。赫夫曼编码的译码的方法,算法设计训练。. 三、教学难点: 构造赫夫曼树和赫夫曼编码的算法。算法设计训练。 四、教学过程:. §5.5 赫夫曼树及其应用. A. B.
E N D
数据结构 (第十二讲) 绍兴文理学院 计算机系计算机应用教研室
第6章 树和二叉树(5) 一、教学目的:明确最优二叉树(赫夫曼树)的概念,掌握构造赫夫曼树和赫夫曼编码方法,掌握构造赫夫曼树和赫夫曼编码的算法。基本掌握对赫夫曼编码的译码的方法,算法设计训练。 二、教学重点:最优二叉树(赫夫曼树)的概念,构造赫夫曼树和赫夫曼编码方法,构造赫夫曼树和赫夫曼编码的算法。赫夫曼编码的译码的方法,算法设计训练。 三、教学难点:构造赫夫曼树和赫夫曼编码的算法。算法设计训练。 四、教学过程:
§5.5 赫夫曼树及其应用 A B C D E F G H I J K L TKS 3 §5.5.1 赫夫曼树的基本概念 1、路径及路径长度 (1) 路径:从树中一个结点到另一个结点之间的分支构成这两个结点之间的路径。 (2) 路径长度:路径上的分支数目。 (3) 树的路径长度:从树根到每一结点的路径长度之和。 n个结点的二叉树的路径长度不小于下述数列前n项的和,即 其路径长度最小者为 在结点数相同的条件下,完全二叉树是路径最短的二叉树。 11:57
2、树的带权路径长度 TKS 4 (1) 权:赋予某个实体的一个量,是对实体的某个或某些属性的数值化描述。在数据结构中,实体有结点(元素)和边(关系)两大类,所以对应有结点权和边权。 带权结点:含权值的结点。根据需要可以给树的结点赋权值。 (2) 结点的带权路径长度:从该结点到树根之间路径长度与结点上权的乘积。 (3) 树的带权路径长度:树中所有叶子结点的带权路径长度之和。记为 3、赫夫曼树(最优二叉树) (1) 概念:设有n个权值量{W1, W2,…, Wn},构造一棵有n个叶子结点的二叉树,每个叶子的结点带权为Wi,则其中WPL最小的二叉树称做最优二叉树(赫夫曼树)。 (2) 相同结点及权值构造不同的二叉树得到不同WPL的示例 11:57
例1一个软件小组四个人,由于业务和工作关系,找组长A频率是8,找副组长B的频率是5,找负责数据库程序员C的频率是3,找负责网络程序员D的频率是2,以这些频率为权的带权叶子结点所构成的三棵二叉树如下:例1一个软件小组四个人,由于业务和工作关系,找组长A频率是8,找副组长B的频率是5,找负责数据库程序员C的频率是3,找负责网络程序员D的频率是2,以这些频率为权的带权叶子结点所构成的三棵二叉树如下: 8 2 5 3 5 8 2 3 (b) (c) (a) D A 8 5 3 2 B A B C D C TKS 5 A B D C 其带权路径长度分别为: (a) WPL=8×2+5×2+3×2+2×2=36 (b)WPL=8×3+5×3+3×2+2×1=47 (c)WPL=8×1+5×2+3×3+2×3=33 可以验证(c)即为赫夫曼树。 若用线性表来组织查找的话,其最小值为35,体现了用构造赫夫曼树来解决这类问题的优越性。 11:58
例2判定问题 及格 不及格 良好 中等 优秀 a 80 a 90 a 60 a 70 TKS 6 设有10000个百分制分数要转换,学生的5个等级成绩分布如下: 例如下面的程序: if(a<60) p=“bad”; else if(a<70) p=“pass”; else if(a<80) p=“general”; else if(a<90) p=“good”; else p=“excellent”; 按图的判定过程: 转换一个分数所需的比较次数=从根到对应结点的路径长度 转换10000个分数所需的总比较次数= 10000(0.051+0.152+0.43+0.34+0.1 4)=31500 11:58
可有不同的判定过程,最佳判定过程为: a<80 Y N a<70 a<90 Y N Y N general good excellent a<60 Y bad pass N TKS 7 总共仅需进行 10000(0.053+0.153+0.42+0.32+0.12)=22000 次比较 ▲此二叉树可以认为是满足一定条件的赫夫曼树。是为了减少比较而使得权值相对较小的结点不在相对层次较低的位置上。 11:58
§5.5.2 赫夫曼树的构造算法 TKS 8 1、构造思想 把所要构造的带权结点都构造在赫夫曼树的叶子位子,权值较大的结点构造在离根结点较近的叶子位子,权值较小的结点构造在离根结点较远的叶子位子,以使得带权路径的长度之和最小。 2、构造方法和过程 (1) 根据给定的n个权值{W1, W2,…,Wn}构成n棵二叉树的集合F={T1, T2,…,Tn},其中每棵二叉树Ti中只有一个带权的Wi的根结点,其左右子树均空。 (2)在F中选取两棵根结点的权值最小的树作为左、右子树构造一棵新的二叉树,且置新的二叉树的根结点的权值为其左、右子树上根结点的权值之和。 (3)在F中删除这两棵树,同时将新得到的二叉树加入F中。 (4)重复(2)和(3),直到F只含一棵树为止,这棵树便是赫夫曼树。 11:58
例:构造以W=(5,15,40,30,10)为权的哈夫曼树。例:构造以W=(5,15,40,30,10)为权的哈夫曼树。 100 10 30 10 10 15 30 40 10 15 15 40 15 30 10 30 40 40 15 40 30 15 60 30 30 15 30 15 60 15 5 5 5 5 5 TKS 9 哈夫曼树中权值小的结点离根远权值大的结点离根近 11:58
3、赫夫曼算法的实现 TKS 10 (1)分析一 ▲没有度为1的结点的树称严格的或正则的二叉树。 ▲ 有n个叶子结点的赫夫曼树共有m=2n-1个结点。(二叉树性质3) ▲用顺序存储结构存储赫夫曼树,其容量为m=2n-1。 ▲ 将n个元素作为叶子结点存入顺序存储结构的1~n的单元中; (2) 存储结构 typedef struct htnode { elmtype data; int weight; int parent,lchild,rchild; }*huffmantree; typedef char **huffmancode; (3)算法思想 11:58
①初始化:将所有单元中的双亲、左孩子,右孩子的下标都初始化为0,再输入前n个单元中叶子结点的权值。 Data weight parent lchild rchild 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 A 5 0 0 0 B 29 0 0 0 C 7 0 0 0 D 8 0 0 0 E 14 0 0 0 F 23 0 0 0 G 3 0 0 0 H 11 0 0 0 - - 0 0 0 - - 0 0 0 - - 0 0 0 - - 0 0 0 - - 0 0 0 - - 0 0 0 - - 0 0 0 TKS 11 5 9 29 14 7 10 ②通过n-l次的选择、删除与合并来创建赫夫曼树: 10 8 选择是从当前森林中选择双亲为0且权值最小的两个树根结点sl和s2; 14 12 删除是指将结点sl和s2的双亲改为非0; 23 13 合并就是将sl和s2的权值和作为一个新结点的权值依次存人到数组的第n+l之后的单元中,同时记录这个新结点左孩子的下标为sl,右孩子的下标为s2。 3 9 11 11 8 8 11 7 1 15 3 4 15 12 19 19 13 9 8 29 29 14 5 10 例5.2已知某系统在通讯联络中只可能出现八种字符(设为A,B,C,D,E,F,G,H),其概率分别为0.05,0.29,0.07,0.08,0.14,0.23, 0.03,0.11,试设计赫夫曼编码。 42 42 11 6 15 58 58 15 2 12 100 13 14 11:58
(4) 算法描述 Data weight parent lchild rchild 5 9 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 A 5 0 0 0 29 14 B 29 0 0 0 7 10 C 7 0 0 0 10 8 D 8 0 0 0 14 12 E 14 0 0 0 23 13 F 23 0 0 0 3 9 G 3 0 0 0 11 11 H 11 0 0 0 8 8 11 7 1 - - 0 0 0 15 3 4 15 12 - - 0 0 0 19 19 13 9 8 - - 0 0 0 29 29 14 5 10 - - 0 0 0 42 42 11 6 15 - - 0 0 0 58 58 15 2 12 - - 0 0 0 100 13 14 - - 0 0 0 TKS 12 huffmantree createhuff(elemtype *w, int n) {huffmantree ht;m=2*n-1; ht=new htnode[m+1]; for(i=1;i<=n;i++) {ht[i].data=w[i-1].data; ht[i].weight=w[i-1].weight; ht[i].parent=0; ht[i].lchild=ht[i].rchild=0; } for(;i<=m;i++) ht[i].weight=ht[i].parent= ht[i].lchild=ht[i].rchild=0; 11:58
Data weight parent lchild rchild 5 9 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 A 5 0 0 0 29 14 B 29 0 0 0 7 10 C 7 0 0 0 10 8 D 8 0 0 0 14 12 E 14 0 0 0 23 13 F 23 0 0 0 3 9 G 3 0 0 0 11 11 H 11 0 0 0 8 8 11 7 1 - - 0 0 0 15 3 4 15 12 - - 0 0 0 19 19 13 9 8 - - 0 0 0 29 29 14 5 10 - - 0 0 0 42 42 11 6 15 - - 0 0 0 58 58 15 2 12 - - 0 0 0 100 13 14 - - 0 0 0 TKS 13 for(i=n+1;i<=m;i++) {select(ht,i-1,&s1,&s2); ht[s1].parent=ht[s2].parent=i; ht[i].lchild=s1;ht[i].rchild=s2; ht[i].weight=ht[s1].weight +ht[s2].weight; } return ht; } 算法5.10 构造赫夫曼树S12_1 11:58
§5.5.2赫夫曼编码 原文 电文(二进制字符串) 原文 发送方 接收方 TKS 14 1、赫夫曼编码的主要思想 (1) 编码问题 在进行数据通讯时,涉及数据编码问题。所谓数据编码就是数据与二进制字符串的转换。例如:邮局发电报: A的编码是B的前缀 例 要传输的原文为ABACCDA 等长编码A:00 B:01 C:10 D:11 发送方: 将ABACCDA 转换成 00010010101100 接收方: 将 00010010101100 还原为 ABACCDA 不等长编码A:0 B:00 C:1 D:01 发送方: 将ABACCDA 转换成 000011010 AAAACCDA 接收方: 000011010 转换成 BBCCDA 11:58
(2)前缀编码 A B B D TKS 15 0 1 为使编码的总长度尽可能的短,若只考虑每个编码尽可能的短,很有可能使一个编码是另一个编码的前缀,从而无法译码。 0 1 所以,构造的编码不应是其它编码的前缀,即任一字符的编码都不是另一字符的编码的前缀,这种编码称做前缀编码。 0 1 可以利用二叉树来设计二进制的前缀编码。假设有一棵如图所示的二叉树,其4个叶予结点分别表示A、B、C、D这4个字符, 若约定左分支表示字符“0”,右分支表示字符“1”, 则根结点到每个叶子结点路径上的0、1序列即为相应字符的编码。可以证明,如此得到的必为二进制前缀编码。 上图所得到的A,B,C,D的编码:0,10,110,111就是前缀编码,所得的译码是唯一的。 11:58
(3) 赫夫曼编码 TKS 16 如何得到编码总长最短的二进制前缀编码呢? 设文中字符出现的次数为Wi,其编码长度为Li,文中有n种字符,则文总长为 ∑wili, 对应到二叉树上,若置wi为叶子结点的权,li即为根到叶子的路径长度。则∑wili为二叉树上带权路径的长度。 据此可得:要得到编码总长最短的二进制前缀编码,就是以n个字符出现的频率为权,设计一棵赫夫曼树的问题。由此得到的二进制前缀编码便称为赫夫曼编码。 2、赫夫曼编码的算法实现 (1) 算法思想 依次以叶子为出发点,向上回溯至根结点为止。回溯时走左分支则生成代码0,走右分支则生成代码1。 即: 11:58
对n个叶结点依次做 Data weight parent lchild rchild 5 9 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 A 5 0 0 0 29 14 B 29 0 0 0 7 10 C 7 0 0 0 10 8 D 8 0 0 0 14 12 E 14 0 0 0 23 13 F 23 0 0 0 3 9 G 3 0 0 0 11 11 H 11 0 0 0 8 8 11 1 7 - - 0 0 0 15 3 4 15 12 - - 0 0 0 19 19 13 8 9 - - 0 0 0 29 29 14 5 10 - - 0 0 0 42 42 6 11 15 - - 0 0 0 58 58 15 2 12 - - 0 0 0 100 13 14 - - 0 0 0 TKS 17 ①当前结点的双亲存在时: 若当前结点是其双亲的左孩子,则产生码‘0’并存储,否则产生码‘1’并存储;并且依次从右向左存储。 ② 双亲作为当前结点(至根结点)。 ③ 存储编码串。 (2) 逆向求每个字符赫夫曼编码的算法描述 11:58
char **huffmancode(huffmantree ht) Data weight parent lchild rchild 5 9 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 A 5 0 0 0 29 14 B 29 0 0 0 7 10 C 7 0 0 0 10 8 D 8 0 0 0 14 12 E 14 0 0 0 23 13 F 23 0 0 0 3 9 G 3 0 0 0 11 11 H 11 0 0 0 8 8 11 1 7 - - 0 0 0 15 3 4 15 12 - - 0 0 0 19 19 13 8 9 - - 0 0 0 29 29 14 5 10 - - 0 0 0 42 42 6 11 15 - - 0 0 0 58 58 15 2 12 - - 0 0 0 100 13 14 - - 0 0 0 TKS 18 {int i,start,c,f; char **hc,*cd; hc=new char *[n+1]; cd=new char[n]; 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[--tart]='0'; else cd[--start]='1'; hc[i]=new char[n-start]; strcpy(hc[i],cd+start); } delete(cd); return hc; } 11:58
例5.2中八种字符的赫夫曼树赫夫曼编码。 100 HC 42 58 A 0001 19 29 F B B 10 29 23 8 H E 15 C 1110 11 14 G A C D D 1111 3 5 7 8 E 110 F 01 G 0000 H 001 TKS 19 0 1 0 1 0 1 0 1 0 1 0 1 0 1 算法5.11 根据赫夫曼树求 赫夫曼编码 S12_2 11:58
附录 关于赫夫曼(David Huffman 戴维 哈夫曼) TKS 20 David Huffman教授1999年10月7日逝世。在他的一生中,他对于有限状态自动机,开关电路,异步过程和信号设计有杰出的贡献。 他发明的Huffman编码能够使我们通常的数据传输数量减少到最小。这个编码的发明和这个算法一样十分引人入胜。 1950年,Huffman在MIT的信息理论与编码研究生班学习。Robert Fano教授让学生们自己决定是参加期未考试还是做一个大作业。而Huffman选择了后者,这个大作业促使了Huffman以后算法的诞生。 他的算法广泛应用于传真机,图象压缩和计算机安全领域。 ? 1、书面作业:P125 2中(2)、(3)、(4) 2、上机编程:P125 3中(4)(8853)、(6)(8855) 五、作业: 11:58