收藏 分享(赏)

数据结构与算法第四章08.ppt

上传人:Facebook 文档编号:3817456 上传时间:2018-11-19 格式:PPT 页数:118 大小:2.62MB
下载 相关 举报
数据结构与算法第四章08.ppt_第1页
第1页 / 共118页
数据结构与算法第四章08.ppt_第2页
第2页 / 共118页
数据结构与算法第四章08.ppt_第3页
第3页 / 共118页
数据结构与算法第四章08.ppt_第4页
第4页 / 共118页
数据结构与算法第四章08.ppt_第5页
第5页 / 共118页
点击查看更多>>
资源描述

1、第四章 二叉树,4.1 二叉树的概念,4.2 二叉树的主要性质,4.3 二叉树的抽象数据类型,4.4 周游二叉树,4.5 二叉树的实现,4.6 二叉搜索树,4.7 堆与优先队列,4.8 哈夫曼编码树,基 本 术 语,结点:,结点的度:,树的度:,叶子结点:,分支结点:,数据元素+若干指向子树的分支,分支的个数,树中所有结点的度的最大值,度为零的结点,度大于零的结点,D,H,I,J,M,(从根到结点的)路径:,结点的层次:,树的深度:,由从根到该结点所经分支和结点构成.若树中存在结点序列k0,k1,ks,使得, ,都是树中的边,则称从结点k0到结点ks存在一条路径。该路径所经历的边的个数称为这条

2、路径的路径长度。,假设根结点的层次为0,第l 层的结点的子树根结点的层次为l+1,树中叶子结点所在的最大层次,孩子结点、双亲结点、 兄弟结点、堂兄弟 祖先结点、子孙结点,在一棵树中,若存在结点k指向结点k的连线,则称k是k的父(双亲)结点,而k则是k的子(孩子)结点,有向连线称作边。 同一个父结点的子结点之间互称兄弟。树中没有父结点的结点称为根。没有子结点的结点称为树叶。 如果结点的双亲结点之间互为兄弟,则这些结点互称堂兄弟。 若有一条由 k到达ks的路径,则称k是ks的祖先,ks是k的子孙。,任何一棵非空树是一个二元组Tree = (root,F) 其中:root 被称为根结点,F 被称为子

3、树森林,森林:,是 m(m0)棵互 不相交的树的集合,A,root,F,对比树型结构和线性结构的结构特点,线性结构,树型结构,第一个数据元素(无前驱),根结点(无前驱),最后一个数据元素(无后继),多个叶子结点(无后继),其它数据元素 (一个前驱、一个后继),其它数据元素 (一个前驱、多个后继),4.1 二叉树的概念,4.1.1 二叉树的定义及相关概念,4.1.2 满二叉树、完全二叉树、 扩充二叉树,二叉树的定义,二叉树由结点的有限集合构成:或者为空集或者由一个根结点及两棵不相交的分别称作这个根的左子树和右子树的二叉树(它们也是结点的集合)组成这是个递归的定义。二叉树可以是空集合,因此根可以有

4、空的左子树或右子树,或者左右子树皆为空,二叉树或为空树;或是由一个根结点加上两棵分别称为左子树和右子树的、互不交的二叉树组成。,根结点,左子树,右子树,E,F,二叉树的五种基本形态:,N,空树,只含根结点,N,N,N,L,R,R,右子树为空树,L,左子树为空树,左右子树均不为空树,满二叉树,如果一棵二叉树的任何结点,或者是树叶,或者恰有两棵非空子树,则此二叉树称作满二叉树,完全二叉树,若一颗二叉树最多只有最下面的两层结点度数可以小于2最下面一层的结点都集中在该层最左边的连续位置上, 则称此二叉树为完全二叉树。,完全二叉树,完全二叉树的特点是: 其叶结点只可能在层次最大的两层出现 完全二叉树中由

5、根结点到各个结点的路径长度总和在具有同样结点个数的二叉树中达到了最小,即任意一棵二叉树中根结点到各结点的最长路径一定不短于结点数目相同的完全二叉树中的路径长度,扩充二叉树,当二叉树里出现空的子树时,就增加新的、特 殊的结点空树叶对于原来二叉树里度数为1的分支结点,在它下面增加一个空树叶对于原来二叉树的树叶,在它下面增加两个空树叶扩充的二叉树是满二叉树,新增加的空树叶 (外部结点)的个数等于原来二叉树的结点(内 部结点)个数加1,扩充二叉树,外部路径长度E 从扩充的二叉树的根到每个外部结点的路径长度之和内部路径长度I 扩充的二叉树里从根到每个内部结点的路径长度之和E和I两个量之间的关系为E=I+

6、2n,扩充二叉树,例如,在图5.3这个有10个内部结点的扩充二叉树里 E = 3 + 4 + 4 + 3 + 4 + 4 + 3 + 4 + 4 + 3 + 3= 39I = 0 + 1 + 2 + 3 + 2 + 3 + 1 + 2 + 3 + 2 = 19 E和I两个量之间的关系为 E = I + 2n。,扩充二叉树,证明:对内部结点数目进行归纳。 当n = 1时,I = 0且E = 2,故E = I + 2n成立。 对于有n个内部结点的扩充二叉树此等式已成立,即En = In+2n,现在考虑有n + 1个内部结点的扩充二叉树 删去一个有两个空树叶的分支结点,该分支结点与根结点的路径长度是

7、K,使之成为有n个内部结点的扩充二叉树。由于删去了一个路径长度为K的内部结点,内部路径长度变为In = In+1-K;由于减少了两个路径长度为K + 1的外部结点,增加了一个路径长度为 K的外部结点,外部路径长度变为En = En+1 - 2 ( K + 1) + K = En+1 K - 2。由前两个等式,有En+1 = In + 2n + K + 2 = In+1 + 2 ( n + 1 )。等式E = I+2n得证。,4.2 二叉树 的主要性质,1.满二叉树定理:非空满二叉树树叶数等于其分支结点数加1。,证明:设二叉树结点数为n,叶结点数为m,分支结点数为b。 有n(总结点数)= m(叶

8、)+b(分支) (公式4.1) 每个分支,恰有两个子结点(满),故有2*b条边; 一颗二叉树,除根结点外,每个结点都恰有一条边联接父结点,故共有n-1条边。即n - 1 = 2b (公式4.2) 由(公式4.1),(公式4.2)得n-1=m+b-1 = 2b,得出m(叶) = b(分支)+ 1,2.满二叉树定理推论:一个非空二叉树的空子树(指针)数目等于其结点数加1。,证明:设二叉树T,将其所有空子树换为树叶,记新的扩充满二叉树为T。所有原来T的结点现在是T的分支结点。 根据满二叉树定理,新添加的树叶数目等于T结点个数加1。而每个新添加的树叶对应T的一个空子树。 因此T中空子树数目等于T中结点

9、数加1。,3.任何一颗二叉树,度为0的结点比度为2的结点多一个,证明:设有n个结点的二叉树的度为0、1、2的结点数分别为=n0,n1,n2,则 n = n0 + n1 + n2 (公式4.3) 设边数为e。因为除根以外,每个结点都有一条边进入,故n = e + 1。 由于这些边是有度为1和2的的结点射出的, 因此e = n1+ 2n2,于是 n = e + 1= n1 + 2n2 + 1 (公式4.4) 因此由公式(1)(2)得 n0 + n1 + n2 = n1 + 2n2 + 1 即n0 = n2 + 1,4. 二叉树的第i层(根为第0层,i1)最多有2i个结点5. 高度为k(深度为k-1

10、。只有一个根结点的二叉树的高度为1,深度为0)的二叉树至多有2k-1个结点6. 有n个结点(n0)的完全二叉树的高度为 (深度为 ),二叉树的高度定义为二叉树中层数最 大的叶结点的层数加1.二叉树的深度定义为二叉树中层数最 大的叶结点的层数.,性质 4 : 在二叉树的第 i 层上至多有2i 个结点。 (i0),用归纳法证明:归纳基:归纳假设:归纳证明:,i = 0 层时,只有一个根结点,2i = 20 = 1;,假设对所有的 j,1 j i,命题成立;,二叉树上每个结点至多有两棵子树, 则第 i 层的结点数 = 2i-1 2 = 2i 。,性质 5 : 高度为 k 的二叉树上至多含 2k-1

11、个结点(k1)。,证明:,基于上一条性质,高度为 k 的二叉树上的结点数至多为20+21+ +2k-1 = 2k-1,性质 6 : 6. 有n个结点(n0)的完全二叉树的高度为 (深度为 ),证明:,设 完全二叉树的高度为 k,则根据性质5得 2k-1-1 n 2k -1,即 k-1 log2 (n+1) k,因为 k 只能是整数,因此, k =,性质 7 :,对于具有n个结点的完全二叉树,结点按层次由左到右编号,则对任一结点i(0 i n - 1)有(1)如果i = 0,则结点i是二叉树的根结点;若i0,则其父结点编号是(i - 1)/2。(2)当2i + 1 n - 1时,结点i的左子结点

12、是2i + 1,否则结点i没有左子结点。当2i + 2 n - 1时,结点i的右子结点是2i + 2,否则结点i没有右子结点。(3)当i为偶数且0 i n时,结点i的左兄弟是结点i - 1,否则结点i没有左兄弟。 当i为奇数且i+1 n时,结点i的右兄弟是结点i + 1,否则结点i没有右兄弟。,证明:这里证明(2),(1) 和(3)即可由结论(2)推得。对于i = 0,由完全二叉树的定义,其左孩子的编号是1,如果1n-1,即不存在编号为1的结点,此时结点i没有左孩子。其右孩子的编号只能是2,如果2n-1,此时结点i没有右孩子。对于i0分两种情况讨论:(1)设第j层的第一个结点编号为i(此时有i

13、 = 2j-1),则其左孩子必为第j + 1层的第一个结点,其编号为2j+1 - 1 = 2i + 1,如果2i + 1 n - 1,那么i没有左孩子;其右孩子必为第j + 1层第二个结点,其编号为2i + 2。(2)假设第j层的某个结点编号为i,若它有左孩子,那么它的左孩子必然是第j + 1层中的第2i - (2j - 1)个,那么其左孩子的编号就是(2j+1 1) + 2i - (2j - 1) = 2i + 1;如果结点i有右孩子,那么其右孩子的编号必是2i + 2。,4.3 二叉树的抽象数据类型,定义了二叉树的逻辑结构之后,我们需要考虑在二叉树逻辑结构之上的各种可能运算,这些运算应该适

14、合二叉树的各种应用:二叉树的某些运算是针对整棵树的初始化二叉树合并两棵二叉树二叉树的大部分运算都是围绕结点进行的访问某个结点的左子结点、右子结点、父结点访问结点存储的数据。,4.3 二叉树的抽象数据类型,二叉树结点抽象数据类型BinaryTreeNode是带有参数T 的模板,T是存储在结点中的数据类型每个元素结点都有leftchild()和rightchild()左右子结点结构另外每个结点还包含一个数据域value()。为了强调抽象数据类型与存储无关,我们并没有具体规定该抽象数据类型的存储方式,4.3 二叉树的抽象数据类型,template class BinaryTreeNode /申明二叉

15、树为结点类的友元类,便于访问 /私有数据成员 friend class BinaryTree; private: /二叉树结点数据域 T element; / 实现时,可以补充private的左子结点 /右子结点定义,4.3 二叉树的抽象数据类型,public: BinaryTreeNode(); /缺省构造函数 BinaryTreeNode(const T,4.3 二叉树的抽象数据类型,/设置当前结点的左子树 void setLeftchild(BinaryTreeNode*) ; /设置当前结点的右子树 void setRightchild(BinaryTreeNode*) ; /设置当前

16、结点的数据域 void setValue(const T,4.3 二叉树的抽象数据类型,二叉树的抽象数据类型的C+定义BinaryTree,没有具体规定该抽象数据类型的存储方式 template class BinaryTree private: /二叉树根结点指针 BinaryTreeNode* root; /从二叉树的root结点开始 /查找current结点的父结点 BinaryTreeNode* GetParent(BinaryTreeNode* root, BinaryTreeNode* current);,4.3 二叉树的抽象数据类型,public: BinaryTree(root

17、=NULL); /构造函数 BinaryTree() DeleteBinaryTree(root);/析构函数 bool isEmpty() const; /判定二叉树是否为空树 /返回二叉树根结点 BinaryTreeNode* Root()return root; /返回current结点的父结点 BinaryTreeNode* Parent(BinaryTreeNode* current); /返回current结点的左兄弟 BinaryTreeNode* LeftSibling( BinaryTreeNode* current);,4.3 二叉树的抽象数据类型,/返回current结点

18、的右兄弟 BinaryTreeNode* RightSibling( BinaryTreeNode* current); / 以elem作为根结点,leftTree作为树的左子树, /rightTree作为树的右子树,构造一棵新的二叉树 void CreateTree(const T /前序周游二叉树或其子树 void PreOrder(BinaryTreeNode* root); /中序周游二叉树或其子树 void InOrder(BinaryTreeNode* root);,4.3 二叉树的抽象数据类型,/后序周游二叉树或其子树 void PostOrder(BinaryTreeNode*

19、 root); /按层次周游二叉树或其子树 void LevelOrder(BinaryTreeNode* root); /删除二叉树或其子树 void DeleteBinaryTree(BinaryTreeNode* root); ;,4.4 周游二叉树,周游:系统地访问数据结构中的结点。每个结点都正好被访问到一次。周游一棵二叉树的过程实际上就是把二叉树的结点放入一个线性序列的过程,或者说把二叉树进行线性化。,二叉树周游4.4.1 深度优先周游二叉树4.4.2 非递归深度优先周游二叉树4.4.3 广度优先周游二叉树,深度优先周游二叉树,我们变换一下根结点的周游顺序,可以得到 以下三种方案:

20、前序周游(tLR次序):访问根结点;前序周游左子树;前序周游右子树。 中序周游(LtR次序):中序周游左子树;访问根结点;中序周游右子树。 后序周游(LRt次序):后序周游左子树;后序周游右子树;访问根结点。,根,左 子树,右 子树,根,根,根,根,根,例如:,前序序列:,中序序列:,后序序列:,A B C D E F G H K,B D C A E H G K F,D C B H K G F E A,深度优先周游二叉树(递归实现),template void BinaryTree:DepthOrder (BinaryTreeNode* root) if(root!=NULL)Visit(ro

21、ot); /前序DepthOrder(root-leftchild(); /访问左子树Visit(root); /中序DepthOrder(root-rightchild();/访问右子树Visit(root); /后序 ,非递归深度优先周游二叉树,栈是实现递归的最常用的结构深度优先二叉树周游是递归定义的利用一个栈来记下尚待周游的结点或 子树,以备以后访问。,非递归前序周游的思想:,遇到一个结点,就访问该结点,并把此结点推入栈中,然后下降去周游它的左子树;周游完它的左子树后,从栈顶托出这个结点,并按照它的右链接指示的地址再去周游该结点的右子树结构。 template void BinaryTr

22、ee:PreOrderWithoutRecusion(BinaryTreeNode* root) /非递归前序遍历二叉树或其子树, using std:stack; /使用STL中的stack stack* aStack; BinaryTreeNode* pointer=root; while(!aStack.empty()|pointer) if(pointer) /访问当前结点 Visit(pointer-value(); /当前结点地址入栈 aStack.push(pointer);,/当前链接结构指向左孩子 pointer=pointer-leftchild(); else /左子树访

23、问完毕,转向访问右子树 pointer=aStack.top(); aStack.pop(); /栈顶元素退栈 /当前链接结构指向右孩子 pointer=pointer-rightchild(); /end while ,非递归中序周游二叉树思想:,遇到一个结点,就把它推入栈中,并去周游它的左子树周游完左子树后,从栈顶托出这个结点并访问之,然后按照它的右链接指示的地址再去周游该结点的右子树。template void BinaryTree:InOrderWithoutRecusion(BinaryTreeNode* root) /非递归中序遍历二叉树或其子树, using std:stack;

24、 /使用STL中的stack stack* aStack; BinaryTreeNode* pointer=root; while(!aStack.empty()|pointer) if(pointer) /当前结点地址入栈 aStack.push(pointer); /当前链接结构指向左孩子 pointer=pointer-leftchild(); /end if,else /左子树访问完毕,转向访问右子树 pointer=aStack.top(); Visit(pointer-value();/访问当前结点 /当前链接结构指向右孩子 pointer=pointer-rightchild()

25、; aStack.pop(); /栈顶元素退栈 /end else /end while ,非递归后序周游二叉树思想:,遇到一个结点,把它推入栈中,周游它的左子树周游结束后,还不能马上访问处于栈顶的该结点,而是要再按照它的右链接结构指示的地址去周游该结点的右子树周游遍右子树后才能从栈顶托出该结点并访问之,解决方案:,需要给栈中的每个元素加上一个特征位,以便当从栈顶托出一个结点时区别是从栈顶元素左边回来的(则要继续周游右子树),还是从右边回来的(该结点的左、右子树均已周游)特征为Left表示已进入该结点的左子树,将从左边回来;特征为Right表示已进入该结点的右子树,将从右边回来,栈中的元素类型

26、定义StackElement enum TagsLeft,Right; /特征标识定义 template class StackElement /栈元素的定义 public: /指向二叉树结点的链接 BinaryTreeNode* pointer; /特征标识申明 Tags tag; ;,template void BinaryTree:PostOrderWithoutRecusion (BinaryTreeNode* root) /非递归后序遍历二叉树或其子树 using std:stack;/使用STL栈部分 StackElement element; stack aStack;/栈申明

27、BinaryTreeNode* pointer; if(root=NULL) return;/空树即返回,/else pointer=root; while(true)/进入左子树while(pointer!=NULL)element.pointer=pointer;element.tag=Left;aStack.push(element);/沿左子树方向向下周游pointer=pointer-leftchild(); /托出栈顶元素 element=aStack.top();,aStack.pop(); pointer=element.pointer; /从右子树回来 while(eleme

28、nt.tag=Right) Visit(pointer-value();/访问当前结点 if(aStack.empty() return; /else element=aStack.top();,aStack.pop();/弹栈 pointer=element.pointer; / /end else /end while /从左子树回来 element.tag=Right; aStack.push(element); /转向访问右子树 pointer=pointer-rightchild(); /end while ,深度优先周游二叉树,对于有n个结点的二叉树,周游完树的每一个元素都需要O(

29、n)时间 只要对每个结点的处理(函数Visit的执行)时间是一个常数,那么周游二叉树就可以在线性时间内完成 所需要的辅助空间为周游过程中栈的最大容量,即树的高度 最坏情况下具有n个结点的二叉树高度为n,则所需要的空间复杂度为O(n),广度优先周游二叉树,从二叉树的第一层(根结点)开始,自上至下逐层遍历;在同一层中,按照从左到右的顺序对结点逐一访问。,对于右图中的二叉树进行广度优先周游的结果为:A B C D E F G H I,template Void BinaryTree:LevelOrder (BinaryTreeNode* root) using std:queue; /使用ATL的队

30、列 queue* aQueue; BinaryTreeNode* pointer=root; if(pointer) aQueue.push(pointer); while(!aQueue.empty(), /取队列首结点 pointer=aQueue.front(); Visit(pointer-value();/访问当前结点 aQueue.pop(); /左子树进队列 if(pointer-leftchild() aQueue.push(pointer-leftchild(); /右子树进队列 if(pointer-rightchild() aQueue.push(pointer-righ

31、tchild(); /end while ,4.5 二叉树的实现,4.5.1 用指针实现二叉树4.5.2 空间开销分析4.5.3 用数组实现完全二叉树4.5.4 穿线二叉树,用指针实现二叉树,二叉树是非线性的树形结构,在存储器里表示树形结构的最自然的方法是链接的方法在每个结点中除存储结点本身的数据外再设置两个指针字段left和right,分别指向结点的左子女和右子女当结点的某个子女为空时,则相应的指针为空指针结点的形式为,二叉树还可以有其他的链接表示法例如,在树的每个结点中除用llink和rlink分别指向子女和兄弟外,再增加一个指向父母的指针parent,形成三重链接的二叉树,称为“三叉链表

32、”有了指向父母的指针就给了我们“向上”访问的 能力,这在一些复杂的应用中是需要的,扩展二叉树结点抽象数据类型BinaryTreeNode,为每个元素结点添加left和right左右子结点结构 private: /二叉树结点指向左子树的指针 BinaryTreeNode* left; /二叉树结点指向左子树的指针 BinaryTreeNode* right;,二叉链表表示的二叉树成员函数实现,template bool BinaryTree: isEmpty() const /判定二叉树是否为空树 return (root)? false :true); template BinaryTreeN

33、ode*BinaryTree:GetParent (BinaryTreeNode* root, BinaryTreeNode* current);,/从二叉树的root结点开始,查找current /结点父结点 BinaryTreeNode* temp; if(root=NULL) return NULL; /找到父结点 if(root-leftchild()=current)| (root-rightchild()=current) return root;,/递归寻找父结点 if(temp=GetParent (root-leftchild(),current)!=NULL) return

34、 temp; else return GetParent (root-rightchild(),current); ,template BinaryTreeNode* BinaryTree:Parent(BinaryTreeNode* current); /返回current结点的父结点指针 if(current=NULL)|(current=root) return NULL;/空结点或者current为根结点时 /调用递归函数寻找父结点 return GetParent(root,current); ,template BinaryTreeNode*BinaryTree:LeftSibli

35、ng (BinaryTreeNode* current) /返回current结点的左兄弟 if(current)/current不为空 /返回current结点的父结点 BinaryTreeNode* temp=Parent(current); /如果父结点为空,或者current没有左兄弟 If(temp=NULL)|current=temp-leftchild() return NULL; else return temp-leftchild();/end if return NULL; ,template BinaryTreeNode* BinaryTree:RightSibling

36、(BinaryTreeNode* current); /返回current结点的右兄弟 if(current) /返回current结点的父结点 BinaryTreeNode* temp=Parent(current); /如果父结点为空,或者current没有右兄弟 if(temp=NULL)|current=temp-rightchild() return NULL; else return temp-rightchild();/end if return NULL; ,templatevoid BinaryTree: CreateTree (const T ,templatevoid B

37、inaryTree: DeleteBinaryTree(BinaryTreeNode* root) /以后序周游的方式删除二叉树或其子树 if(root) DeleteBinaryTree(root-left);/递归删除左子树 DeleteBinaryTree(root-right);/递归删除右子树 delete root;/删除根结点 ;,空间开销分析,结构性开销是指为了实现数据结构而花费的辅助空间比例。这些辅助空间不是用来存储数据记录,而是为了保存数据结构的逻辑特性或为了方便运算。,存储密度 (1)表示数据结构存储的效率显然 = 1 - 如果所有的存储空间都分配给了数据,则这个存储结构

38、叫紧凑结构,否则叫非紧凑结构,紧凑结构的存储密度为1, 非紧凑结构的存储密度小于1显然二叉链表的存储是非紧凑结构。 存储密度越大,则存储空间的利用效率越高,空间开销分析,根据满二叉树定理:一半的指针是空的如果只有叶结点存储数据,分支结点为内部结构结点(如Huffman树),则开销取决于二叉树是否满(越满存储效率越高)对于简单的每个结点存两个指针、一个数据域 总空间(2p + d)n 结构性开销:2pn如果p = d,则2p/(2p + d) = 2/3,空间开销分析,去掉满二叉树叶结点中的指针则结构性开销为1/2 (假设p = d )如果只在叶结点存数据,则结构性开销为2pn/(2pn+ dn

39、/2)=4/5 (假设p = d )注意:区分叶结点和分支结点又需要额外的算法时间。,空间开销分析,在C+中,可以用两种方法来实现不同的分支结点与叶结 点:用union联合类型定义使用C+的子类来分别实现分支结点与叶结点,并采用虚函数isLeaf来区别分支结点与叶结点早期有人利用结点指针的一个空闲位(一个bit就可以)来存储结点所属的类型也有人利用指向叶结点的指针或者叶结点中的指针域来存储该叶结点的值。目前,计算机内存资源并不紧张的时候,一般不提倡这种单纯追求效率,而丧失可读性的做法,用数组实现完全二叉树,当我们要求一个二叉树紧凑存储,并且在处理过程中,该二叉树结构的大小和形状不发生激烈的动态

40、变化时,可以采用顺序的方法存储用顺序方法存储二叉树,就是要把所有结点按照一定的次序 顺序存储到一片连续的存储单元中适当安排这个结点的线性序列,可以使结点在序列中的相互位置反映出二叉树结构的部分信息但一般说来这样的信息是不足以刻画整个结构的,还要在结点中附加一些其他的必要信息,以完全地反映整个结构,用数组实现完全二叉树,按层次顺序将一棵有n个结点的完全二叉树的所有结点从0到n-1编号,就得到结点的一个线性序列,完全二叉树的下标公式,若对含 n 个结点的完全二叉树从上到下且从左至右进行 1 至 n 的编号,则对完全二叉树中任意一个编号为 i 的结点: (1) 若 i=0,则该结点是二叉树的根,无双

41、亲, 否则,编号为 (i-1)/2 的结点为其双亲结点; (2) 若 2i+1n,则该结点无左孩子, 否则,编号为 2i+1 的结点为其左孩子结点; (3) 若 2i+2n,则该结点无右孩子结点, 否则,编号为2i+2 的结点为其右孩子结点。 当i为偶数且0in时,结点i的左兄弟是结点i-1,否则结点i没有左兄弟当i为奇数且i+1n时,结点i的右兄弟是结点i+1,否则结点i没有右兄弟,完全二叉树的顺序存储结构,图5.9 完全二叉树的节点编号,图5.10 完全二叉树的顺序存储结构,如图所示,按层次顺序将一棵n个结点的完全二叉树中的所有结点从0到n-1编号。可以将这棵二叉树编号为i的结点元素存储在

42、一维数组下标为i的分量中。例如左图所示的二叉树的顺序存储结构如右图所示。,对于任何一个二叉树结点,如果它存储在数组的第i个位置,那么它的左右子结点分别存放在2i + 1和2i + 2的位置,它的父结点的下标为(i-1) / 2。,完全二叉树的顺序存储总结,完全二叉树结点的层次序列足以反映二叉树的结构所有结点按层次顺序依次存储在一片连续的存储单元中,则根据一个结点的存储地址就可算出它的左右子女,父母的存储地址,就好像明显地存储了相应的指针一样存储完全二叉树的最简单,最节省空间的存储方式完全二叉树的顺序存储,在存储结构上是线性的,但在逻辑结构上它仍然是二叉树型结构,穿线二叉树,穿线树:在二叉链表存

43、储形式的二叉树中,把结点中空指针利用成为周游线索,原来为空的左指针指向结点在某种周游序列下的前驱原来为空的右指针指向结点在同一种周游序列下的后继 这样的二叉树称为穿线树可以有中序穿线树,前序穿线树,后序穿线树。每种穿线树可以只穿一半目的:利用空指针的存储空间,建立周游线索,穿线二叉树,为了区分线索和指针,需在每个结点中增加两个标志位,分别标识左右指针域是实际指针还是线索lTag = 0, left为左子女指针lTag = 1, left为前驱线索rTag= 0, right为右子女指针rTag = 1, right为后继指针,中序穿线二叉树:示例,中序周游穿线树,中序周游中序穿线树:先从穿线树

44、的根出发,一直沿左指针,找到“最左”(它一定是中序的第一个结点);然后反复地找结点的中序后继一个结点的右指针如果是线索,则右指针就是下一个要周游的结点,如果右指针不是线索,则它的中序后继是其右子树的“最左”结点,穿线树总结,任何包括n个结点的二叉树的二叉链表中,2n个指针中都只有n-1个用来指示结点的左右子女,而另外n+1个为空。这显然是浪费存储空间的利用空指针空间的一个办法就是用指向结点在中序下的前驱结点和后继结点的指针来代替这些空的指针穿线树的最大优点是:由于有了线索的存在而使得周游二叉树和找结点在指定次序下的前驱、后继的算法变得直截了当,4.6 二叉搜索树,二叉搜索树(BST)或者是一颗

45、空树;或者是具有下列性质的二叉树:对于任何一个结点, 设其值为K,则该结点的左子树(若不空)的任意一个结 点的值都小于K;该结点的右子树(若不空)的任意一个 结点的值都大于或等于K;而且它的左右子树也分别为 二叉搜索树二叉搜索树的性质: 按照中序周游将各结点打印 出来,将得到按照由小到大的排列,二叉搜索树,二叉搜索树的效率就在于只需检索二个子树之一从根结点开始,在二叉搜索树中检索值K。如果根结点储存的值为K,则检索结束。如果K小于根结点的值,则只需检索左子树如果K大于根结点的值,就只检索右子树这个过程一直持续到K被找到或者我们遇上了一个 树叶如果遇上树叶仍没有发现K,那么K就不在该二叉 搜索树

46、中,二叉搜索树的插入,往二叉搜索树里插入新结点,要保证插入后仍符合二叉搜索树的定义插入是这样进行的:将待插入结点的关键码值与树根的关键码值比较,若待插入的关键码值小于树根的关键码值,则进入左子树,否则进入右子树在子树里又与子树根比较,如此进行下去,直到把新结点插入到二叉树里作为一个新的树叶,二叉搜索树,对于给定的关键码集合,为建立二叉搜索树,可以从一个空的二叉搜索树开始,将关键码一个个插进去将关键码集合组织成二叉搜索树,实际上起了对集合里的关键码进行排序的作用,按中序周游二叉搜索树,就能得到排好的关键码序列。,二叉搜索树的删除,从二叉搜索树里删除一个结点时,不能把以这个结点 为根的子树都删除掉

47、,只能删除掉这一个结点,并且还要保持二叉搜索树原来的性质。设p,p1,r是指针变量,p表示s要删除的结点, p1表示p的父母结点,则删除可以按如下规定进行:若结点p没有左子树,则用右子树的根代替被删除的结点p若结点p有左子树,则在左子树里找按中序周游的最后一个结点r,用r的右子树的根取代r,然后用结点r去代替被删除的结点p,从二叉排序树中删除wan和zol后得到的二叉排序树,二叉搜索树总结,树形结构的一个重要应用是用来组织索引, 二叉搜索树是适用于内存储器的一种重要的 树形索引 二叉搜索树的插入和删除运算非常简单。往 二叉搜索树里插入新结点或删除已有结点, 要保证操作结束后仍符合二叉搜索树的定

48、义,4.7 堆与优先队列,最小值堆:最小值堆是一个关键码序列 K0,K1,Kn-1,它具有如下特性:KiK2i+1 (i=0,1, n/2-1)KiK2i十2类似可以定义最大值堆,堆的示例,堆的性质,堆实际上是一个完全二叉树的层次序列,可以用数组表示堆中储存的数是局部有序的结点储存的值与其子女储存的值之间存在某种联系。有两种不同的堆,决定于其关于联系的定义堆中任何一个结点与其兄弟之间都没有必然的联系堆不唯一。从逻辑角度看,堆实际上是一种树型结构,建堆过程筛选法,不必将值一个个地插入堆中,通过交换形成堆假设根的左、右子树都已是堆,并且根的元素名为R。这种情况下,有两种可能:(1) R的值小于或等于其两个子女,此时堆已完成;(2) R的值大于其某一个或全部两个子女的值,此时R应与两个子女中值较小的一个交换,结果得到一个堆,除非R仍然大于其新子女的一个或全部的两个。这种情况下,我们只需简单地继续这种将R“拉下来”的过程,直至到达某一个层使它小于它的子女,或者它成了叶结点,

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

当前位置:首页 > 中等教育 > 小学课件

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


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

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

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