算法与数据结构(c语言)第5章二叉树与树.ppt
《算法与数据结构(c语言)第5章二叉树与树.ppt》由会员分享,可在线阅读,更多相关《算法与数据结构(c语言)第5章二叉树与树.ppt(129页珍藏版)》请在三一办公上搜索。
1、第五章 二 叉 树与树,树形结构是一种十分重要的数据结构。本章讨论的二叉树、树和树林都属于树形结构。在树形结构中每个结点最多只有一个前驱,但可有多个后继的结构。它们的共同之处是都表示了一种具有层次的分支关系。,5.1 二叉树及其抽象数据类型,二叉树是一类简单而又重要的树形结构。本节先介绍它的基本概念和重要性质,然后引入二叉树的抽象数据类型。,基本概念,二叉树可以定义为结点的有限集合,这个集合或者为空集,或者由一个根及两棵不相交的分别称作这个根的左子树和右子树的二叉树组成。二叉树的定义是个递归定义。二叉树可以是个空集合,这时的二叉树称为空二叉树。二叉树也可以是只有一个结点的集合,这个结点只能是根
2、;它的左子树和右子树均是空二叉树。,下图表示的是二叉树的五种基本形态。,二叉树相关的一组术语:父结点、左(右)子结点、边 兄弟 祖先、子孙 路径、路径长度 结点的层数 结点的度数 二叉树的高度:二叉树中结点的最大层数称为二叉树的高度。例如,二叉树t的高度为3。树叶、分支结点,满二叉树:如果一棵二叉树的任何结点或者是树叶,或有两棵非空子树,则此二叉树称作满二叉树(离散数学中称此树是正则的)。完全二叉树:如果一棵二叉树至多只有最下面的两层结点度数可以小于2,其余各层结点度数都必须为2,并且最下面一层的结点都集中在该层最左边的若干位置上,则此二叉树称为完全二叉树。完全二叉树不一定是满二叉树。,扩充的
3、二叉树:把原二叉树的结点都变为度数为2的分支结点,也就是说,如果原结点的度数为2,则不变,度数为1,则增加一个分支,度数为0(树叶),则增加两个分支。新增加的结点(树叶结点)都用小方框表示,称为外部结点,树中原有的结点称为内部结点。把空二叉树扩充得到的扩充二叉树规定为只有一个外部结点组成的二叉树。,在扩充的二叉树中,外部路径长度E定义为在扩充的二叉树中从根到每个外部结点的路径长度之和。内部路径长度I定义为在扩充的二叉树中从根到每个内部结点的路径长度之和。,如在图的扩充二叉树中:E=2+2+4+4+3+3+3=21 I=0+1+1+2+2+3=9,5.1.2 主要性质,性质1 在非空二叉树的i层
4、上至多有2i个结点(i0)。证明:用归纳法来证。性质2 高度为k的二叉树中最多有2k+1-1个结点(k0)。证明:假设第i层上的最大结点个数是mi,由性质1可知,深度为k的二叉树中最大结点个数M为:,性质3 对于任何一棵非空的二叉树,如果叶结点个数为n0,度为2的结点个数为n2,则有:n0=n2+1。证明:设一棵非空二叉树中有n个结点,度为1的结点个数为n1,因为二叉树中所有结点的度均不大于2,所以 n=n0+n1+n2(1)在二叉树中,除根结点外,其余每个结点都有一个分支进入,假设B为分支总数,则有B=n 1(2)又由于二叉树中的分支都是由度为1和2的结点发出的,所以有B=n1+2n2(3)
5、综合(1)、(2)、(3)式可得n0=n2+1,性质4 具有n个结点的完全二叉树的深度k为。证明:根据性质2和完全二叉树的定义可知2k-1 n 2k+1 1即:2k n 2k+1 对不等式取对数有:k k+1由于k为整数,所以有 k=,性质5 对于具有n个结点的完全二叉树,如果按照从上(根结点)到下(叶结点)和从左到右的顺序对二叉树中的所有结点从0开始到n-1进行编号,则对于任意的下标为i的结点,有:(1)如果i=0,则它是根结点,它没有父结点:如果i0,则它的父结点的下标为(i-1)/2;(2)如果2i1n1,则下标为i的结点的左子结点的下标为2i1;否则,下标为i的结点没有左子结点:(3)
6、如果2i+2n1,则下标为i的结点的右子结点的下标为2i+2;否则,下标为i的结点没有右子结点。,性质6 在满二叉树中,叶结点的个数比分支结点个数多1。性质7 在扩充二叉树中,外部结点的个数比内部结点的个数多1。,性质8 对任意扩充二叉树,外部路径长度E和内部路径长度I之间满足以下关系:E=I+2n,其中n是内部结点个数。,5.1.3 二叉树的抽象数据类型,ADT BinTree isoperationsBinTree createEmptyBinTree(void)创建一棵空的二叉树。BinTree consBinTree(BinTreeNode root,BinTree left,BinT
7、ree right)返回一棵二叉树,其根结点是root,左右二叉树分别为left和right。int isNull(BinTree t)判断二叉树t是否为空。,BinTreeNode root(BinTree t)返回二叉树t的根结点。若为空二叉树,则返回一特殊值。BinTreeNode parent(BinTree t,BinTreeNode p)返回结点p的父结点。当指定的结点为根时,返回一个特殊值。BinTree leftChild(BinTree t,BinTreeNode p)返回p结点的左子树,当指定结点没有左子树时,返回一个特殊值。BinTree rightChild(BinTr
8、ee t,BinTreeNode p)返回p结点的右子树,当指定结点没有右子树时,返回一个特殊值。end ADT BinTree 由于二叉树的概念是递归定义的,二叉树中的每个结点也可标识以这个结点为根的二叉树,所以二叉树类型和二叉树中结点类型在具体实现时常常看成是同一种类型。在关于二叉树的周游等算法中,采用这种观点描述特别方便。,5.2 二叉树的周游,什么是周游二叉树的周游是指按某种方式访问二叉树中的所有结点,使每个结点被访问一次且只被访问一次。深度优先周游广度优先周游,5.2.2 周游的分类,深度优先周游,若以符号D、L、R分别表示根结点、左子树、右子树,则二叉树的周游共有六种方式:DLR,
9、LDR,LRD,DRL,RDL和RLD。如果限定先左后右,则只能采用前三种周游方式,即DLR,LRD和LDR,它们分别被称为先根次序(简称先序或前序)周游、后根次序(简称后序)周游和中根次序(简称中序)周游(也叫对称序次序周游)。,先根次序:,若二叉树不空,则先访问根;然后按先根次序周游左子树;最后按先根次序周游右子树。下图所示二叉树,先根次序周游得到的结点序列为:A,B,D,C,E,G,F,H,I,后根次序,若二叉树不空,则先按后根次序周游左子树;然后按后根次序周游右子树;最后访问根。前图所示的二叉树,按后根次序周游得到的结点序列为:D,B,G,E,H,I,F,C,A,对称(中根)次序,若二
10、叉树不空,则先按对称序周游左子树;然后访问根;最后按对称序周游右子树。对于前图所示的二叉树,按对称序周游一棵二叉树得到的结点序列为:D,B,A,E,G,C,H,F,I,通常将按先根次序对一棵二叉树周游得到的线性表称为这棵二叉树的先根序列。通常将按后根次序周游一棵二叉树得到的线性表称为这棵二叉树的后根序列。通常将按对称次序周游一棵二叉树得到的线性表称为这棵二叉树的对称(中根)序列,对于给定的二叉树,可以唯一确定它的先根序列、后根序列和对称序列。但是反过来,给定一个二叉树的任意一种周游的序列,无法唯一确定这个二叉树。一般而言,如果已知一个二叉树的对称序列,又知道另外一种周游序列(无论是先根序列还是
11、后根序列),就可以唯一确定这个二叉树。,广度优先周游若二叉树的高度为h,则从0到h逐层如下处理:从左到右逐个访问存在的结点。前图的二叉树,广度优先周游所得到的结点序列为:A,B,C,D,E,F,G,H,I广度优先周游一棵二叉树所得到的结点序列,叫作这棵二叉树的层次序列。,5.2.4 周游的抽象算法,这里给出的算法,都是建立在上节关于抽象数据类型基本运算的基础上。它们不依赖于二叉树的具体存储结构,所以称为抽象算法。,递归算法,三种深度优先周游的递归算法:,先根次序周游:void preOrder(BinTree t)中根次序周游:void inOrder(BinTree t)后根次序周游:voi
12、d postOrder(BinTree t),非递归算法,利用栈的帮助,可以写出各种深度优先周游的非递归算法。由于本节给出的算法,基本都不涉及具体的存储结构,所以对于栈或者队列的使用,算法中也不依赖于具体的表示方式。读者在实习时,需要经过适当精化后,才能上机运行。,先根次序周游非递归,采用非递归算法实现先根次序周游二叉树的主要思路是:首先把根结点压入栈中;然后从栈顶中取出元素(包括退栈),只要取出元素非空,就访问该结点,然后顺序将其右子结点和左子结点进栈,重复执行上述过程,直到当从栈顶中取出的元素(包括退栈)为空,并且栈也为空时,周游结束。程序实现:void nPreOrder(BinTree
13、 t)算法中每个二叉树恰好进栈、出栈各一次,所以它的时间代价为O(n),其中n为二叉树中子二叉树(也是结点)的个数。,对称序周游非递归,采用非递归算法实现对称序周游二叉树的主要思路是:若二叉树不为空时,则沿其左子树前进,在前进过程中,把所经过的二叉树逐个压入栈中,当左子树为空时,弹出栈顶元素,并访问该二叉树的根,如果它有右子树,再进入当前二叉树的右子树,从头执行上述过程;如果它没有右子树,则弹出栈顶元素,从前面继续执行。直到当前二叉树为空并且栈也为空时,周游结束。程序实现:void nInOrder(BinTree t),后根次序周游非递归,与先根次序周游及对称序周游二叉树的方法不同,在后根次
14、序周游的过程中,对一个二叉树的根结点访问之前,要两次经过这个二叉树:首先是由该二叉树找到其左子树,周游其左子树,周游完返回到这个二叉树;然后是由该二叉树找到其右子树,周游其右子树,周游完再次返回到这个二叉树,这时才能访问该二叉树的根结点。,从上面的分析中可以看出,在后根次序周游二叉树时,一个二叉树可能要进、出栈各两次,只有在它第二次出栈之后,才可以访问该二叉树的根结点。为了区分同一二叉树的两次出栈,需要给栈中二叉树增加一标志量tag,定义为:若tag=1,则本二叉树是第一次进栈,所有下次出栈,不能访问本二叉树的根结点;若tag=2,则本二叉树是第二次进栈,所有下次出栈,应该访问本二叉树的根结点
15、。,二叉树进、出栈时,其标志量tag也同时进、出栈,因此,可以把二者合并成一个结构变量,将栈中元素的数据类型设置为下面的形式:typedef struct int tag;/*标记*/BinTree t;/*二叉树*/Elem;程序实现:void nPostOrder1(BinTree t)这里虽然是一个两重循环,但是每个子树进栈和出栈两次,所以时间代价仍然是O(n)。,以上算法可以从两个方面加以改进:一方面是取消标志tag,以节省栈的空间;另一方面是每个二叉树只进栈和出栈一次,以节省执行时间。为此必须在算法中二叉树出栈时增加判断;如果是从栈顶二叉树的左子树回来,就直接进入右子树周游,如果是从
16、栈顶二叉树的右子树回来,就执行出栈,访问该二叉树的根结点。程序实现:void nPostOrder2(BinTree t),广度优先周游(非递归),根据广度优先周游的思想不难想到,可以利用一个队列实现其算法:首先把二叉树送入队列;其后,每当从队首取出一个二叉树访问之后,马上把它的子二叉树按从左到右的次序送入队列尾端;重复此过程直到队列为空。程序实现:void levelOrder(BinTree t),每个二叉树进队列一次出队列一次,所以时间代价为O(n)。主要空间代价是需要队列的附加空间。若二叉树结点个数为n,最坏的情况出现在完全二叉树时,需要大约n/2个队列元素的空间。,5.3 二叉树的实
17、现,顺序表示 链接表示 线索二叉树,5.3.1 顺序表示,二叉树的顺序表示,也是采用一组连续的存储单元来存放二叉树中的结点,但是,由于二叉树是非线性结构,所以结点之间的逻辑关系难以从存储的先后确定。不过,由二叉树的性质5可知,对于完全二叉树,如果按照从上(根结点)到下(叶结点)和从左到右的顺序,对二叉树中的所有结点从0到n-1编号,这样存放到一维数组中。只要通过数组元素的下标关系,就可以确定二叉树中结点之间的逻辑关系。,完全二叉树及其顺序表示,对于一般的二叉树,如果仍采用顺序表示,首先要对它进行扩充,增加一些并不存在的空结点,使之成为一棵完全二叉树,然后再用一维数组顺序存储。在二叉树中人为增加
18、的空结点,在数组所对应的元素中,可以用一个特殊值表示。,采用顺序存储表示的二叉树,可用下列方式定义:,struct SeqBinTree/*顺序二叉树类型定义*/int MAXNUM/*完全二叉树中允许结点的最大个数*/int n;/*改造成完全二叉树后,结点的实际个数*/DataType*nodelist;/*存放结点的数组*/;typedef struct SeqBinTree*PSeqBinTree;/*顺序二叉树类型的指针类型*/,在上述采用顺序表示的二叉树中,nodelist数组中的每个元素表示二叉树中的一个结点,以这个结点为根的子二叉树的根就是它自己,它的左子树以nodelist2
19、*i+1为根,它的右子树以nodelist2*(i+1)为根,它的父结点则是nodelist(i-1)/2(前提是这些结点存在)。n是扩充成完全二叉树以后结点的实际个数。当二叉树为空二叉树时,n=0。MAXNUM是完全二叉树中允许结点的最大个数,它可以作为数组空间的大小参数提供给二叉树的创建函数使用,也可以在数组溢出时,通过程序扩充空间。,运算的实现,返回下标为p的结点的父结点的下标 int parent_seq(PSeqBinTree t,int p)返回下标为p的结点的左子结点的下标 int leftChild_seq(PSeqBinTree t,int p)返回下标为p的结点的右子结点的
20、下标 int rightChild_seq(PSeqBinTree t,int p),5.3.2 链接表示,二叉树的链接表示是用一个链表来存储一棵二叉树,二叉树中的每个结点对应链表中的一个结点。由于二叉树是非线性结构,每个结点最多有两个后继,所以最常用的链接表示方式是左-右指针表示法,这种表示法在每个结点中,除了存储结点本身的数据外,再设置两个指针字段:llink和rlink,分别存放结点的左子结点和右子结点的位置,当结点的某个子树为空时,则相应的指针为空指针。,struct BinTreeNode;/*二叉树中结点*/typedef struct BinTreeNode*PBinTreeNo
21、de;/*结点的指针类型*/struct BinTreeNode DataType info;/*数据域*/PBinTreeNode llink;/*指向左子结点*/PBinTreeNode rlink;/*指向右子结点*/;,由于递归是二叉树的固有特性,二叉树的许多处理都可以用递归算法来描述,因此,为了运算和参数传递的方便,不再对二叉树进行封装,直接将二叉树定义为指向结点的指针类型:typedef struct BinTreeNode*BinTree;在实际应用中,将二叉树作为参数传递时,可能需要传递二叉树根结点指针的地址,因此,为了说明方便,可以引入二叉树类型的指针类型:typedef B
22、inTree*PBinTree;,运算的实现,返回结点p的左子结点的地址 PBinTreeNode leftChild_link(PBinTreeNode p)返回结点p的右子结点的地址 PBinTreeNode rightChild_link(PBinTreeNode p),实现求父结点的操作就比较困难,只能从t出发,使用前面介绍的周游算法,通过算法中的访问函数(visit),检查当前结点是否所求结点的父结点。最坏的时间代价与周游整个二叉树的代价相同。为了提高求父结点操作的速度,可以采用的另一种链接表示方式是三叉链表表示,即给二叉树中的每个结点增加一个指向父结点的指针域。采用三叉链表表示,既
23、便于查找子结点,又便于查找父结点,但是相对于左右指针表示而言,它增加了空间开销。,5.3.3 线索二叉树,线索二叉树是对于左右指针表示法的一种修改。它利用结点的空的左指针(llink)存储该结点在某种周游序列中的前驱结点的位置;利用结点的空的右指针(rlink)存储该结点在同种周游序列中的后继结点的位置。这种附加的指向前驱结点和后继结点的指针称作线索,加进了线索的二叉树左右指针表示称作线索二叉树。把二叉树左右指针表示改造成线索二叉树的过程称为线索化。,为区分左右指针和线索,需要在每个结点里增加两个标志位ltag和rtag,令:ltag=0,则llink是指针,指向结点的左子结点;ltag=1,
24、则llink是线索,指向结点的对称序的前驱结点;rtag=0,则rlink是指针,指向结点的右子结点;rtag=1,则rlink是线索,指向结点的对称序的后继结点。这样,增加了标志域的结点结构为:,struct ThrTreeNode;/*线索二叉树中的结点*/typedef struct ThrTreeNode*PThrTreeNode;/*指向线索二叉树结点的指针类型*/struct ThrTreeNode/*线索二叉树中结点的定义*/DataType info;PThrTreeNode llink,rlink;int ltag,rtag;typedef struct ThrTreeNod
25、e*ThrTree;/*线索二叉树类型的定义*/typedef ThrTree*PThrTree;/*线索二叉树类型的指针类型*/,按对称序线索化二叉树,在未线索化之前,所有结点的llink和rlink都是指向子结点指针,因此所有ltag和rtag的初始状态都为0。给出一棵二叉树,要将它按对称序线索化,其做法就是按对称序周游此二叉树,在周游的过程中用线索代替空指针。void thread(ThrTree t),用线索树的最大优点是:由于有了线索的存在,在某些情况下可以很方便地找到指定结点在某种周游序列中的前驱和后继,而不必再对二叉树重新周游。另外,在线索树上进行某种次序周游要比在一般二叉树上进
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 算法 数据结构 语言 二叉
![提示](https://www.31ppt.com/images/bang_tan.gif)
链接地址:https://www.31ppt.com/p-6012004.html