收藏 分享(赏)

二叉树树.ppt

上传人:无敌 文档编号:1107847 上传时间:2018-06-11 格式:PPT 页数:69 大小:1.92MB
下载 相关 举报
二叉树树.ppt_第1页
第1页 / 共69页
二叉树树.ppt_第2页
第2页 / 共69页
二叉树树.ppt_第3页
第3页 / 共69页
二叉树树.ppt_第4页
第4页 / 共69页
二叉树树.ppt_第5页
第5页 / 共69页
点击查看更多>>
资源描述

1、,1,数 据 结 构 Ch.6 树,计 算 机 学 院 肖明军Email: http:/ 树,树形结构:二叉树,树,森林等结点间有分支,具有层次关系 特征:每个结点最多只有一个直接前驱,但可有多个直间后继开始结点 根终端结点 叶其余结点 内部结点应用:家谱、行政架构等,计算机系统中的文件目录等,3,.树的概念,Def: 树是n (n=0) 个结点的有限集,为空时称为空树,否则它满足:有且仅有一个特定的称为根的结点;其余结点可分为m (m=0) 个互不相交的子集1 ,2,m,其中每个子集本身又是一棵树,并称之为根的子树,4,.树的概念,递归定义:刻画了树的固有特性:非空树由若干棵子树构成,子树由

2、较小子树构成表示:,5,.树的概念,术语:结点的度:结点拥有的子树数目(树的度)叶子:终端结点,度为的结点分支结点:非终端结点,度内部结点:根之外的分支结点根:开始结点孩子、双亲:某结点的子树的根称为该 结点的孩子,该结点为孩子的双亲 直接前驱(双亲) 直接后继(孩子)兄弟:同一双亲的孩子互为兄弟,6,.树的概念,术语:路径:道路(自上而下) 若存在一结点序列k1,k2,,kj,使得ki是ki+1的双亲(=i=0) 棵互不相交的树的集合 树和森林非常接近:删去树根 森林 森林加上一根 树,8,.树的概念,逻辑特征:父子关系(非线性关系):任一结点至多有一直接前驱(双亲)结点,但可有多个直接后继

3、(子女)结点开始结点:根终端结点:叶祖先与子孙关系:是对父子关系的延拓,它定义了树中结点的纵向关系横向关系:有序树定义了同一组兄弟间的从左到右的长幼关系,可将其延拓到结点间的横向次序:k1和k2是兄弟,k1在左,则k1的任一子孙在k2的任一子孙的左边,其余结点为内部结点,9,.二叉树,是一种特殊的树型结构,每个结点至多只有二棵子树,一般树可转换为二叉树,计算机用途甚广.二叉树的定义ef: 二叉树是n(n=0)个结点的有限集,它或者是空集,或由一个根结点及两棵互不相交的,分别称作根的左子树和右子树的二叉树组成,10,.1 二叉树的定义,形态与度为的有序树区别:,当某一结点只有一孩子时,有序树中它

4、理所当然为长子,但二叉树中,一个结点只有一个孩子亦需分出其左右,11,.2 二叉树的性质,性质:二叉树的第i层上至多有2i-1个结点(i1,根为第1层) pf:归纳法归纳基础:i=1,2i-1=1,第1层上只有根,故成立归纳假设:设所有的j(1 ji)命题成立即: 第j层结点数j-1归纳步骤:j=i时,第i-1层结点数 i-2(由归纳假设) 因为,每个结点至多有两个孩子 所以,第i层上结点数 i-2=2i-1,12,.2 二叉树的性质,性质深度为h的二叉树至多有h-1个结点(h1).Pf: 深度一定时,仅当每层上结点达到最大时,该树结点最多 利用性质1知,深度为h的二叉树至多有: 20+21+

5、2h-1 = 2h-1,13,.2 二叉树的性质,性质在任一二叉树中,设叶子数为n0,度为的结点数为n2则n0=n2+1. Pf: 设n1为度为的结点总数,则结点总数n等于度, 度和度结点数之和 n = n0+n1+n2 / 二叉树 (.)另一方面,除根外,其余结点均是其双亲的孩子,树中: 孩子结点总数 = n1+2n2 n-1 = n1+2n2 n = n1+2n2+1 (.)由.和.:n0=n2+1,14,.2 二叉树的性质,满二叉树:深度为h的具有h-1个结点的二叉树称为满二叉树完全二叉树:若一二叉树至多只有最下两层上结点的度数可小于,且最下一层上的结点都集中在该层最左边的若干位置,则称

6、为完全二叉树某结点无左孩子,则它必为叶子满二叉树是完全二叉树,但反之未必成立,15,.2 二叉树的性质,性质具有n个结点的完全二叉树高为 或pf: 树高为h,则前h-1层是高为h-1的满二叉树,结点总数 = 2h-1-1. 2h-1-1 n 2h-1 (2h-1 n+1 2h) / 第h层上至少有一个结点 2h-1 n 2h /整数 h-1 lgn h (h-1 1,则ki的双亲是 (2) 若2in,则ki的左孩子为k2i;否则ki无左孩子,即它必 为叶子。/因此,完全二叉树中编号i 的结点必为叶子; (3) 若2i+1n,则ki的右孩子为k2i+1 ,否则ki无右孩子; (4) 若i为奇数且

7、大于1,则ki的左兄弟为ki-1,否则ki无左兄 弟; (5) 若i为偶数且小于n,则ki的右兄弟为ki+1,否则ki无右兄 弟。 证明从略。,18,6.2.3 二叉树的存储结构, 上述关系中,编号足以反 映结点间的逻辑关系 可将n个结点存储在向量 bt0.n中,其中: bt0 不用,或存储n bt1.n 存储编号为1至n的结点,19,6.2.3 二叉树的存储结构,缺点 对一般的二叉树,须按完全二叉树的编号来存储,浪费空间,最坏情况是右单支树,k个结点需2k-1个结点空间。结论 这种结构只适用于存储完全二叉树,且插入和删除不便。,20,6.2.3 二叉树的存储结构,链式存储结构结点结构类型定义

8、 typedef struct node DataType data; struct node *lchild, *rchild; BinTNode; / 结点类型 typedef BinTNode *BinTree; /二叉树类型,21,6.2.3 二叉树的存储结构,在二叉树中,所有类型为BinTNode的结点,加上一个指向根的BinTree型头指针root,就构成了二叉树的链式存储结构,称之为二叉链表 显然,二叉链表由根指针root唯一确定。例子特性 空树 root = NULL 叶子 左右指针为空 空指针数:n个结点,共有2n个指针域,但只有n-1个结点是别人的孩子,故空指针数为n+1,

9、22,6.3 遍历二叉树,概念定义:沿某搜索路线,依次对树中每个结点均做一次且仅做一次访问。重要性:是其它运算的基础,很多树上操作均依赖于遍历操作,只是访问结点所做的操作不同。如何遍历?遍历线性结构很容易:从开始结点出发,依次访问当前结点的后继,直至终端结点为止。遍历路线只有一条(如单链表,从头指针开始)。 但二叉树中每个结点可能有两个后继,故遍历路线不唯一,须找到适用于每个结点的相同的遍历规则。,23,6.3 遍历二叉树, 在二叉树的递归定义中,非空树组成为: D、L、R 在任一结点上,可按某种次序执行三个操作: 访问根结点(D),遍历该结点的左子树(L),遍历该结点的右子树(R) 显然有六

10、种执行次序: 遍历规则(从左到右) DLR,LDR,LRD的差别是访问根的先后次序不同前序(先序,先根)遍历:DLR中序(中根)遍历:LDR后序(后根)遍历:LRD,24,6.3 遍历二叉树,遍历算法 以中根为例,遍历二叉树定义为: if 二叉树非空 then (1) 遍历左子树 / 即遍历二叉树 (2) 访问根 / 将(1)(2)和(2)(3)对调后为先根和后根遍历 (3) 遍历右子树 /即遍历二叉树 / 否则为空操作 (递归结束条件) void Inorder(BinTree T) / T为二叉树的头指针 if (T) / T非空,T为空时为空操作 Inorder(T-lchild); /

11、 递归遍历左子树 printf(“%c”, T-data);/ 访问根结点,具体问题,此 / 操作不同 Inorder(T-rchild); / 递归遍历右子树 时间:O(n),25,6.3 遍历二叉树,遍历序列包络线是递归遍历路线向下:表示递归调用,更 深一层向上:表示递归结束,返 回一层 每个结点经过3次,第1次经过时访问所得结点序列为前序,第2次经过时访问所得结点序列为中序,第3次经过时访问所得结点序列为后序。,1个开始结点,1个终端结点,其余结点均有一个直接前驱和一个直接后继,为区别3种次序在前面冠以,叶子的相对次序相同,26,6.3 遍历二叉树,通用的遍历算法 因为访问结点的操作依赖

12、于具体问题,故可将它作为一个函数指针参数放于遍历算法的参数表中,调用时,使其指向具体的访问结点的应用函数 void Inorder( BinTree T, void (*Visit)(DataType x) ) if (T) Inorder(T-lchild, Visit); (*Visit)(T-data); Inorder(T-rchild, Visit); 其中Visit是一函数指针,它指向形如void f(DataType x)的函数,故可将访问结点的操作写在函数f中,通过调用语句: Inorder(root, f); 将f的地址传给Visit。,27,6.3 遍历二叉树,通用的遍历算

13、法 例如,可将打印操作为 void print(DataType x) print(“%c”, x); 调用Inorder(root, print),即可完成前述算法。函数名:函数代码的起址。,28,6.3 遍历二叉树,建立二叉链表 上述算法假定二叉链表已建立 建立二叉树对应的二叉链表方法很多先序遍历构造法 输入先序序列,加入虚结点(输入时用空格符“ ”)以示空指针的位置,例如前述的二叉树,输入为: ABDCEF,29,6.3 遍历二叉树,void CreateBinTree(BinTree *T) / 注意T为指针的指针 char ch; if (ch = getchar() = n) re

14、turn; / 回车结束输入 if (ch = ) / 读入空格 *T = NULL; / 将相应的指针置空 else / 读入的是结点数据 *T = (BinTNode*) malloc(sizeof(BinTNode); (*T)-data = ch; / 生成新结点,相当于访问根节点 CreateBinTree( / 遍历右子树 建树时调用 CreateBinTree(&root), 将root(BinTree类型)的地址复制给T,故修改*T就相当于修改了实参root本身。 时间:O(n),30,6.4 线索二叉树,基本概念 在一基本数据结构上常常需要扩充,增加辅助信息,其目的是:开发新

15、操作;加速已有操作。线索 利用空指针域(n+1个)存放指向结点在某种遍历次序下的前驱和后继指针线索链表 加上线索的二叉链表线索二叉树 相应的二叉树称为线索二叉树目的:加速遍历操作 加速查找任一结点在某种遍历次序下的前驱和后继操作如何区别结点的指针域 孩子指针:指向孩子? 线索指针:指向其某种遍历次序下的前驱和后继的线索?,31,6.4 线索二叉树,基本概念结点结构线索标志中序线索树和中序线索链表,中序序列:CBDAFHGIE,32,6.4 线索二叉树,基本概念中序线索树中必有两个指针域为空前序线索树中,有几个指针域为空? 前序序列的开始结点为根,故当它的左子树非空时,其指针域指向左指子树,此时

16、前序序列开始结点左指针非空。 但是,前序序列的终端结点的右指针必为空。 仅当只有1个根结点或根的左子树为空时有两个空指针。,33,6.4 线索二叉树,基本概念后序线索树中,开始结点的左指针必为空,仅当只有1个根时或根的右子树为空时有两个空指针。 与前序线索树对称带头结点的线索链表 树上6.11图(b). 令空指针也指向此哨兵,34,6.4 线索二叉树,基本概念线索树中,如何判定结点是否为叶子? ltag = rtag = 1 (适用于三种线索树)有时线索树只有左线索或右线索之一线索化将二叉树变为线索树的过程按某种次序遍历,在遍历过程中用线索取代空指针。 typedef enum Link, T

17、hread PointerTag; / 0为Link,1为Thread typedef struct node DataType data; PointerTag ltag, rtag; struct node *lchild, *rchild; BinThrNode, *BinThrTree;设p和pre分别指向遍历过程中当前访问结点和其前驱,即*pre和*p是 前驱和后继的关系,其中pre为全局量,在遍历过程中建立线索。以中序为例,pre初始为NULL,因为中序前驱对开始结点是NULL。,35,6.4 线索二叉树,void InorderThreading(BinThrTree p) /

18、pre为全局量,初值为NULL if (p) InorderThreading(p-lchild); / 左子树线索化 p-ltag = (p-lchild) ? Link : Thread; / 左指针非空,置为 / Link,否则为线索。 p-rtag = (p-rchild) ? Link : Thread; if (pre) / 若*pre存在 if (pre-rtag = Thread) / 当前结点*p的前驱右标志为线索 pre-rchild = p; / 令*pre的右线索指向中序后继*p if (p-ltag = Thread) / 当前结点的左线索已建立 p-lchild =

19、 pre; /令当前节点的左线索指向中序前驱 pre = p; /使*pre为*p的前驱,循环不变量 InorderThreading(p-rchild); / 右子树线索化 时间:和遍历相同O(n). 后序(前序)线索化类似于此。,访问根结点,36,6.4 线索二叉树,操作(加速) (1) 找某结点*p(中序线索树中)中序前驱和后继结点找*p的中序后继i) 若*p的右子树空,则p-rchild为右线索,直接指向*p的中序后继ii)若*p的右子树非空(rtag为0),则*p的中序后继必是其右子树中第一个中序遍历到的结点,其特征是: 从*p的右孩子开始,沿其左链往下找,直至找到一个没有左孩子的结

20、点为止,不妨称其为右子树中“最左下”的结点。,这里k 1, Rk不一定是叶子,其右子树可为空,可非空。算法 请自己给出找给定结点*p的中序后继算法。时间O(h),快于无线索的二叉树。,p,37,6.4 线索二叉树,找*p的中序前驱 中序是对称序,其方法与(1)完全对称 i) 若*p的ltag = 1,则中序前驱为lchild ii)若*p的ltag = 0,则中序前驱是*p的左子树中 “最右下”的结点。,加速作用 在普通二叉树中,找*p中序后继: 对于ii)同样有效 对于 i)就必须从根开始遍历。 有线索是直接从线索找到O(1)。但线索一般“向上”指向其祖先,而二叉树中无向上的链接,只能从根开

21、始遍历得到,最坏情况O(n)。,38,6.4 线索二叉树,(2) 找*p的后序前驱和后序后继(在后序线索树中)找*p的后序前驱(易) i) 若*p的ltag = 1(左子树为空),则后序前驱为p-lchild ii)若*p的ltag = 0(左子树非空),则后序前驱为p的左孩子或右孩子 根是在遍历左右子树L和R之后被访问 *p的前驱必是L和R中最后一个遍历到的结点 if (p的右孩子非空) then return p-rchild; / 后序前驱为p的右孩子 else return p-lchild; /后序前驱为p的左孩子找*p的后序后继(难)(见右图) i) 若*p为根,无后继,返回NUL

22、L ii) 若*p是其双亲右子,则*p的后序后继是双亲 iii)若*p是其双亲左子,但*p无右兄弟,则*p的 后序后继亦为双亲,39,6.4 线索二叉树,找*p的后序后继(续)iv)若*p是其双亲左子,但*p有右兄弟,则*p的 后序后继是其右兄弟子树中1st后序遍历到的结点,它是该子树中“最左下的叶结点”结论:找后序前驱易 找后序后继难,因为: 只有*p的右子树为空(rtag = 1)时,p-rchild是线索,可直接找到; 否则,一般须涉及*p的双亲,故仅给出*p时,须从根遍历。 (3) 找*p的前序前驱和前序后继 类似于后序的情况分析 讨论:找前序前驱难(涉及双亲) 找前序后继易,40,6

23、.4 线索二叉树,(4) 遍历线索二叉树 遍历某次序的线索二叉树,只要从该次序下的开始结点出发,反复找其后继直至终端结点为止。中序 找开始结点(最左下结点),找当前结点中序后继,直至终端结点(p-rchild = NULL) 头结点的右指针指向开始结点较方便前序 找开始结点(根),找当前结点的前序后继,直至终端结点 (p-rchild = NULL) 头结点的右指针指向终端结点较方便后序 找终端结点(根),找当前结点的后序前驱,直至开始结点 (p-lchild = NULL),得到的是后序序列的逆序列 头结点的右指针指向开始结点较方便时间:仍为O(n),但因为非递归,略快于递归的方法对遍历而言

24、 前序线索树中,只需保留右线索树即可 中序线索树中,保留左、右线索之一均可 后序线索树中,只需保留左线索即可,41,6.5 树和森林,树、森林与二叉树的转换树 = 二叉树 树中每个结点有多个孩子 = 二叉树只有两个孩子 长子及右邻兄弟 = 二叉树的左右孩子 节点X的长子是其左子,X的右兄弟是其右子每个结点仅保留与长子的连线所有兄弟结点间连线,42,6.5 树和森林,树、森林与二叉树的转换森林 = 二叉树将各树转换为二叉树(根无右兄弟,所以无右子)将各根作为兄弟互连,43,6.5 树和森林,树、森林与二叉树的转换二叉树 = 树或森林设x是y的左孩子,则将x的右孩子,右孩子的右孩子,都与y相连去掉

25、所有双亲到右孩子的连线,44,6.5 树和森林,树的存储结构双亲链表表示法 每结点双亲唯一,故存储结点时,增加一个parent域指向双亲,用向量表示较方便特点:向上链接,根的parent为-1 求指定结点双亲(O(1)及祖先(O(h)方便 求指定结点孩子及后代须遍历数组O(n)类型说明(略),45,6.5 树和森林,树的存储结构孩子链表表示法若k叉树用k叉链表表示,会导致浪费空间 树边n-1条 空指针kn-(n-1)=n(k-1)+1设度数域,结点不等长、运算不便孩子链表:每结点设一孩子链表,将结点及相应孩子链表的头指针放在一向量中。,46,6.5 树和森林,树的存储结构孩子链表表示法(续)特

26、点:易实现找结点的孩子及子孙(向下查找易) 难实现找结点的双亲及祖先(向上查找难)双亲孩子链表表示法 在孩子链表中,增加parent域 此方法结合了双亲链表和孩子链表的优点,向上向下查找均方便类型说明:略孩子兄弟链表表示法 树 = 二叉树时,结点关系由:最左孩子、右邻兄弟表 示,47,6.5 树和森林,树和森林的遍历先序遍历树 先访问树的根;然后依次先序遍历根的每棵子树后序遍历树 先依次后序遍历根的每棵子树;然后访问树的根;先序遍历森林先访问森林中第一棵树的根;然后先序遍历第一棵树中根结点的各子树所构成的森林;最后先序遍历除第一棵外其它树构成的森林后序遍历森林后序遍历森林中第一棵树的根结点的各

27、子树所构成的森林;然后访问第一棵树的根;最后后序遍历除第一棵外其它树构成的森林先序遍历恰好等价于先序遍历对应的二叉树,后序遍历恰好等价于中序遍历对应的二叉树。,48,6.6 Huffman树及其应用,6.6.1 最优二叉树概念结点路径长度:根到该结点所经过的边数树的路径长度:所有结点的路径长度之和 (结点数相同时,完全二叉树的路径长度最短)结点的带权路径长度: 结点的权值Wi 结点的路径长度li树的带权路径长度(实际上是加权外部路径长度) 所有叶子的加权路径长度之和,49,6.6 Huffman树及其应用,6.6.1 最优二叉树最优二叉树(Huffman树) 在权为w1,w2,wn的叶子所构成

28、的所有的二叉树,WPL最小的二叉树称为最优二叉树 例 n=4,a:7, b:5, c:2, d:4若叶子权值相同,完全二叉树一定是最优二叉树,否则不一定,50,6.6 Huffman树及其应用,6.6.1 最优二叉树构造最优二叉树 显然,权越大应离根越近,Huffman首先提出了构造方法:1)初始森林:根据给定的n个权w1,w2,wn构成一个包含n棵二叉树的森林F=T1,T2,Tn. 其中Ti只有一个根(亦为叶子)结点,其权为wi;2)合并:在F中选两棵根的权最小的二叉树(若不止两棵,则任选)作为左、右孩子,将其合并为一棵新树,新根权值为这两个结点的权值之和;3)对新森林F重复2),直至只剩下

29、一棵树为止。,51,6.6 Huffman树及其应用,6.6.1 最优二叉树构造最优二叉树算法特点初始有n棵树,每棵树仅有一个孤立结点合并:每合并一次,少一棵树,故只需合并n-1次 每合并一次产生1新结点,且新结点度为2,故最终的 Huffman树有2n-1个结点: 实际上任何严格二叉树中,叶子数为n时,总结点数为2n-1。,52,6.6 Huffman树及其应用,6.6.1 最优二叉树构造最优二叉树存储结构 #define n 100 / 叶子数 #define m 2*n-1 / 结点总数 typedef struct float weight; / 权,不妨设0 int lchild,

30、rchild, parent; / 指针 HTNode; typedef HTNode HuffmanTreem; parent域作用:找双亲结点 为-1时表示根,区分根与非根构造算法,53,6.6 Huffman树及其应用,void CreateHuffmanTree (HuffmanTree T) / 构造最优树,根为Tm-1 int i, p1, p2; InitHuffmanTree(T); / 初始化, 将2n-1个结点的3个指针置空(-1),权置为0 InputWeight(T); / 输入n个叶子(根)的权存于T0.n-1中,初 /始化森林F0中的根权 for (i = n; i

31、 平均码长最小前缀码 树中任一叶子不可能是其它叶子的祖先 每叶子编码不可能是其它叶子编码的前缀算法实现 (对字符集编码,即叶子集编码) typedef struct char ch; / 放字符 char bitsn+1; / 放位串0结束,码长不会超过n CodeNode; / 存放Huffman编码的结点 typedef CodeNode HuffmanCoden;,59,6.6.2 Huffman编码,void HuffmanCoding (HuffmanTree T, HuffmanCode H) / 根据哈夫曼树T求哈夫曼编码表H int c, p, i; / c和p分别指示树T中孩子和父亲的位置 char cdn+1; / 临时存放编码 int start; / 指示编码在cd中的起始位置 cdn = 0; / 从后往前放编码 for (i = 0; i = 0) / 到根为止 if (Tp.lchild = c) cd-start = 0; / 若Tc是Tp的左子树生成代码0 else cd-start = 1; / 否则生成代码1 c = p; / 继续上溯 strcpy(Hi.bits, / 复制编码位串 / endfor / 时间: O(n.h),

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 企业管理 > 经营企划

本站链接:文库   一言   我酷   合作


客服QQ:2549714901微博号:道客多多官方知乎号:道客多多

经营许可证编号: 粤ICP备2021046453号世界地图

道客多多©版权所有2020-2025营业执照举报