280 likes | 465 Views
第 6 章 树和二叉树( Tree & Binary Tree ). 6.1 树的基本概念 6.2 二叉树 6.3 遍历二叉树和线索二叉树 6.4 树和森林 6.5 赫夫曼树及其应用. n+1. 问: 用二叉链表法( l_child, r_child )存储包含 n 个结点的二叉树,结点的指针区域中必有 个空指针。. 分析: ① n 个结点必有 2n 个链域; (见二叉链表数据类型说明) ②除根结点外,二叉树中每一个结点 有且仅有一个双亲 (直接前驱),所以只会有 n - 1 个结点的链域存放指针,指向非空子女结点(即直接后继)。.
E N D
第6章 树和二叉树( Tree & Binary Tree ) 6.1 树的基本概念 6.2 二叉树 6.3 遍历二叉树和线索二叉树 6.4 树和森林 6.5 赫夫曼树及其应用
n+1 问:用二叉链表法(l_child, r_child)存储包含n个结点的二叉树,结点的指针区域中必有个空指针。 分析: ①n个结点必有2n个链域; (见二叉链表数据类型说明) ②除根结点外,二叉树中每一个结点有且仅有一个双亲(直接前驱),所以只会有n-1个结点的链域存放指针,指向非空子女结点(即直接后继)。 所以, 空指针个数=2n-(n-1)=n+1个。 思考:二叉链表空间效率这么低,能否利用这些空闲区存放有用的信息或线索?
增加两个域:fwd和bwd; 两种解决方法: 二、线索二叉树(Threaded Binary Tree) 1. 定义 2. 生成 3. 遍历 普通二叉树只能找到结点的左右孩子信息,而该结点的直接前驱和直接后继只能在遍历过程中获得。 若将遍历后对应的有关前驱和后继预存起来,则从第一个结点开始就能很快“顺藤摸瓜”而遍历整个树了。 例如中序遍历结果:B D C E A F H G,实际上已将二叉树转为线性排列,显然具有唯一前驱和唯一后继。 如何预存这类信息? 存放前驱指针 存放后继指针 利用空链域(n+1个空链域)
规定: 1)若结点有左子树,则lchild指向其左孩子; 否则, lchild指向其直接前驱(即线索); 2)若结点有右子树,则rchild指向其右孩子; 否则, rchild指向其直接后继(即线索)。 LTag RTag 左孩子或前驱 右孩子或后继 为区别两种不同情况,特增加两个标志域(各1bit) 约定: 当Tag域为0时,表示正常情况; 当Tag域为1时,表示线索情况.
附:有关线索二叉树的几个术语: 线索链表:用含Tag的结点样式所构成的二叉链表 线 索:指向结点前驱和后继的指针 线索二叉树:加上线索的二叉树 线 索 化:对二叉树以某种次序遍历使其变为线索二叉树的过程 讨论:增加了前驱和后继等线索有什么好处? ——能方便找出当前结点的前驱和后继,不用堆栈也能遍历整个树。
例:某先序遍历结果如下表所示,请画出对应的二叉树。例:某先序遍历结果如下表所示,请画出对应的二叉树。 (多带了两个标志!) A G H E D C F J I B
2. 线索二叉树的生成 线索化过程就是在遍历过程中修改空指针的过程: 将空的lchild改为结点的直接前驱; 将空的rchild改为结点的直接后继。 非空指针呢?仍然指向孩子结点(称为“正常情况”)
A root C B G F E D I H 例1:画出以下二叉树对应的中序线索二叉树。 解:该二叉树中序遍历结果为:H, D, I, B, E, A, F, C, G 所以添加线索应当按如下路径进行: 悬空? 为避免悬空态,应增设一个头结点 悬空?
对应的中序线索二叉树存储结构如图所示: root -- 0 0 A 0 0 C B 0 0 0 0 1 G F E 1 D 1 1 1 0 1 0 H 1 1 I 1 1 注:此图中序遍历结果为: H, D, I, B, E, A, F, C, G
28 25 33 NIL NIL 40 60 08 54 55 例2:【 2000年计算机系考研题】给定如图所示二叉树T,请画出与其对应的中序线索二叉树。 解:因为中序遍历序列是:55 40 25 60 28 08 33 54 对应线索树应当按此规律连线,即在原二叉树中添加虚线。
线索二叉树的生成算法(算法6.6, 见教材P134) 目的:在依某种顺序遍历二叉树时修改空指针,添加前驱或后继。 注解:为方便添加结点的前驱或后继,需要设置两个指针: p指针→当前结点之指针; pre指针→前驱结点之指针。 技巧:当结点p的左/右域为空时,只改写它的左域(装入前驱pre),而其右域(后继)留给下一结点来填写。 或者说,当前结点的指针p应当送到前驱结点的空右域中。 若p->lchild=NULL,则{p->Ltag=1;p->lchild=pre;} //p的前驱结点指针pre存入左空域 若pre->rchild=NULL, 则{pre->Rtag=1;pre->rchild=p;} //p存入其前驱结点pre的右空域
① ② ③ ④ ⑤ ⑦ ⑥ ⑧ ⑨ ⑩…… 3. 线索二叉树的遍历 理论上,只要找到序列中的第一个结点,然后依次访问结点的后继直到后继为空时结束。 但是,在线索化二叉树中,并不是每个结点都能直接找到其后继的,当标志为0时,R_child=右孩子地址指针,并非后继!需要通过一定运算才能找到它的后继。 以中序线索二叉树为例: 对叶子结点(RTag=1),直接后继指针就在其rchild域内; 对其他结点(RTag=0),直接后继是其右子树最左下的结点; (因为中序遍历规则是LDR,先左再根再右)
线索二叉树的中序遍历算法(算法6.5, 参见教材P134) 程序注解 (非递归,且不用栈): P=T->lchild; //从头结点进入到根结点; while( p!=T) {while(p->LTag==link)p=p->lchild; //先找到中序遍历起点 if(!visit(p->data)) return ERROR; //若起点值为空则出错告警 while(p->RTag==Thread ……){ p=p->rchild; Visit(p->data);} //若有后继标志,则直接提取p->rchild中线索并访问后继结点; p=p->rchild; //当前结点右域不空或已经找好了后继,则一律从结点的右子树开始重复{ }的全部过程。 } Return OK; LTag=0 RTag=1
p=T->lchild; N p!=T Y N p->LTag==0 return OK; Y p=p->lchild; vist(p->data); N p->LTag==1&&p->rchild!=T Y p=p->rchild;visit(p->data); p=p->rchild; 算法流程: 演 示 程 序
提前介绍:二叉树的应用 平衡树—— 排序树—— 字典树—— 判定树—— 带权树—— 最优树—— 特点:左右子树深度差 ≤1 特点:“左小右大”(见实验二的方案1) 由字符串构成的二叉树排序树 例如,12个球只称3次分出轻重 特点:路径长度带权值 带权路径长度最短的树,又称 Huffman树,用途之一是通信中的压缩编码。
a c b g f e d 6.5 Huffman树及其应用 一、最优二叉树(霍夫曼树) 预备知识:若干术语 路 径: 路径长度: 树的路径长度: 带权路径长度: 树的带权路径长度: 霍 夫 曼 树: 由一结点到另一结点间的分支所构成 路径上的分支数目 a→e的路径长度= 2 从树根到每一结点的路径长度之和。 树长度= 10 结点到根的路径长度与结点上权的乘积 树中所有叶子结点的带权路径长度之和 带权路径长度最小的树。
Weighted Path Length n WPL = wklk k=1 7 2 c a 5 4 2 4 7 5 b c d d b a 2 4 5 7 (a) b c d a (b) (c) Huffman树简介: 树的带权路径长度如何计算? 哈夫曼树则是:WPL 最小的树。 经典之例: Huffman树 WPL= 35 WPL=36 WPL=46
构造霍夫曼树的基本思想: 权值大的结点用短路径,权值小的结点用长路径。 构造Huffman树的步骤(即Huffman算法): (1)由给定的 n 个权值{w0, w1, w2, …, wn-1},构造具有 n 棵扩充二叉树的森林F = { T0, T1, T2, …, Tn-1 },其中每一棵扩充二叉树 Ti 只有一个带有权值 wi 的根结点,其左、右子树均为空。 (2)重复以下步骤, 直到 F 中仅剩下一棵树为止: ① 在 F 中选取两棵根结点的权值最小的扩充二叉树, 做为左、右子树构造一棵新的二叉树。置新的二叉树的根结点的权值为其左、右子树上根结点的权值之和。 ② 在 F 中删去这两棵二叉树。 ③ 把新的二叉树加入 F。
例1:设有4个字符d,i,a,n,出现的频度分别为7,5,2, 4,怎样编码才能使它们组成的报文在网络中传得最快? 法1:等长编码。例如用二进制编码来实现。 取 d=00,i=01,a=10,n=11 法2:不等长编码,例如用哈夫曼编码来实现。 取 d=0; i=10, a=110, n=111 最快的编码是哪个? 是非等长的Huffman码! 怎样实现Huffman编码? 先要构造Huffman树!
构造Huffman树的步骤: 操作要点1:对权值的合并、删除与替换 ——在权值集合{7,5,2,4}中,总是合并当前值最小的两个权 注:方框表示外结点(叶子,字符对应的权值), 圆框表示内结点(合并后的权值)。
0 1 d 0 1 i 1 0 a n 操作要点2:按左0右1对Huffman树的所有分支编号! ——将 Huffman树 与 Huffman编码 挂钩 Huffman编码结果:d=0, i=10, a=110, n=111 WPL=1bit×7+2bit×5+3bit(2+4)=35 特点:每一码都不是另一码的前缀,绝不会错译! 称为前缀码
霍夫曼编码的基本思想是:概率大的字符用短码,概率小的用长码。由于霍夫曼树的WPL最小,说明编码所需要的比特数最少。这种编码已广泛应用于网络通信中。霍夫曼编码的基本思想是:概率大的字符用短码,概率小的用长码。由于霍夫曼树的WPL最小,说明编码所需要的比特数最少。这种编码已广泛应用于网络通信中。 例2(严题集6.26③):假设用于通信的电文仅由8个字母 {a, b, c, d, e, f, g, h} 构成,它们在电文中出现的概率分别为{ 0.07, 0.19, 0.02, 0.06, 0.32, 0.03, 0.21, 0.10},试为这8个字母设计哈夫曼编码。如果用0~7的二进制编码方案又如何? 解:先将概率放大100倍,以方便构造哈夫曼树。权值集合 w={7, 19, 2, 6, 32, 3, 21, 10}, 按哈夫曼树构造规则(合并、删除、替换),可得到哈夫曼树。
× × × × 100 × × × × 60 × × 32 × × 40 × × 19 21 g e b 2 3 7 10 a d h f c 为清晰起见,重新排序为:w={2, 3, 6, 7, 10, 19, 21, 32} w1={5, 6, 7, 10, 19, 21, 32} w2={7, 10, 11, 19, 21, 32} w3={11, 17, 19, 21, 32} w4={19, 21, 28, 32} 28 w5={28,32,40} w6={40,60} 17 11 w7={100} 6 5 哈夫曼树
100 60 1 0 28 32 0 0 1 1 17 11 40 0 1 6 5 0 1 0 1 19 21 g e b 1 0 2 3 7 10 a d h f c 对应的哈夫曼编码(左0右1): Huffman码的WPL=2(0.19+0.32+0.21) + 4(0.07+0.06+0.10) +5(0.02+0.03) =1.44+0.92+0.25=2.61 WPL=3(0.19+0.32+0.21+0.07+0.06+0.10+0.02+0.03)=3 二进制码
字符 空格 a b c d e f g h i 频度 186 64 13 22 32 103 21 15 47 57 字符 j k l m n o p q r s 频度 1 5 32 20 57 63 15 1 48 51 字符 t u v w y z x 频度 80 23 8 18 1 16 1 例3(实验二方案3):设字符集为26个英文字母,其出现频度如下表所示。 要求编程实现: 先建哈夫曼树,再利用此树对报文“This program is my favorite”进行编码和译码。
提示1:霍夫曼树中各结点的结构可以定义为如下5个分量:提示1:霍夫曼树中各结点的结构可以定义为如下5个分量: 提示2:霍夫曼树的存储结构可采用顺序存储结构: 将整个霍夫曼树的结点存储在一个数组中:HT[1..n]; 将结点的编码存储在HC[1..n]中。 提示3:霍夫曼树如何构造?构造好之后又如何求得各结点对应的霍夫曼编码?——算法参见教材P147。
树 顺序结构 二叉链表 链式结构 二叉树 三叉链表 森林 先序遍历 中序遍历 后序遍历 先序线索树 中序线索树 后序线索树 霍夫曼树 二叉树小结 1、定义和性质 2、存储结构 3、遍历 4、线索化:线索树 霍夫曼编码