610 likes | 805 Views
第 5 章 树. 1. 树的概念. 2. 二叉树. 3. 二叉树的遍历. 4. 二叉树的其他运算. 5. 树的存储结构和运算. 主要内容. 5.1 树的概念. 5.1.1 树的定义 5.1.2 树的表示 5.1.3 树的基本术语 5.1.4 树的性质. 5.1.1 树的定义. 一种非常重要的非线性结构。 定义 : 或者是一棵空树,或者是一棵非空树,在一棵非空树中: 有且仅有一个特定的结点,称为树的 根 (root)
E N D
1 树的概念 2 二叉树 3 二叉树的遍历 4 二叉树的其他运算 5 树的存储结构和运算 DS 主要内容
DS 5.1 树的概念 • 5.1.1 树的定义 • 5.1.2 树的表示 • 5.1.3 树的基本术语 • 5.1.4 树的性质
DS 5.1.1 树的定义 • 一种非常重要的非线性结构。 • 定义:或者是一棵空树,或者是一棵非空树,在一棵非空树中: • 有且仅有一个特定的结点,称为树的根(root) • 其余结点可分为m(m>0)个互不相交的有限集T1,T2,……Tm,其中每一个集合本身又是一棵树,称为根的子树(subtree) • 每个子树的根结点是整个树根结点的后继,而整个树根结点又是所有子树根结点的前驱。 • 树的定义是递归的,树是一种递归的数据结构。
A B C D F E E G H H I I DS 根 举例: T2 T1 F D 子树T1又可以分为三棵子树:T11,T12,T13 T13 T11 T12
DS 5.1.1 树的定义 • 在一棵树中,每个结点被定义为它的每个子树的根结点的前驱,而它的每个子树的根结点就成为它的后继。 • 二元组形式定义: tree=(K,R) K={ki|1≤i≤n,n≥0,ki∈ElemType} 关系R应满足: (1)有且仅有一个结点没有前驱,该结点被称为树的根。 (2)除树根结点外,其余每个结点有且仅有一个前驱结点。 (3)包括树根结点在内的每个结点,可以有任意多个后继。
王庭贵 王万胜 王万利 王家新 王家中 王家国 DS 5.1.1 树的定义 • 日常生活和计算机领域有很多树结构的例子: • 【例5-1】可把家族看作一棵树,树中的结点是成员的姓名及相关信息,树中的关系为父子关系。即父亲是儿子的前驱,儿子是父亲的后继。
工学院 信息学院 机械学院 。。。 电气学院 计算机 软件 电子 DS 5.1.1 树的定义 • 【例5-2】可把一个地区或一个单位的组织结构看作一棵树,树中的结点为机构名及相关信息,树中的关系为上下级关系。如一个城市分成若干个区,每个区又分为若干个街道,每个街道又分为若干个居委会。
DS 5.1.1 树的定义 • 【例5-3】可把一本书的结构看作一棵树,树中的结点为书、章、节的名称及相关信息,树中的关系为包含关系。 • 【例5-4】可把一个算术表达式表示成一棵树,运算符作为根结点,它的前后两个运算对象分别作根的左、右两棵树。 • 【例5-5】在计算机领域,每个逻辑盘上信息组织的目录结构就是一棵树,树中的结点为包含有目录名或文件名的每个目录项或文件项,树中的根目录用反斜线表示,根目录下包含有若干个子目录项和文件项,每个子目录下又包含有若干个子目录项和文件项,以此类推。
DS 5.1.2 树的表示 • 树形表示法 最常用的表示法,结点之间的关系是通过连线表示的,虽然连线上不带箭头,但方向隐含为从上向下或从左向右。 • 二元组表示法 • 集合图表示法 每棵树对应一个圆形,圆内包含根结点和子树。 • 凹入表表示法 每棵树的根对应着一个条形,子树的根对应着较短的条形,且树根在上,子树的根在下。 • 广义表表示法 每棵树的根作为由子树构成的表的名字而放在表的前面。
A A B E C A D H I F G B C B D D F E E G H I H I F C G DS Tree={K,R} K={A,B,C,D,E,F,G,H,I} R={<A,B>,<A,C>,<B,D>,<B,E>,<B,F>,<c,G>,<E,H>,<E,I>} 二元组表示法 树形表示法 凹入表表示法 集合图表示法 A(B(D,E(H,I),F),C(G)) 广义表表示法
A B C D F E G H I DS 5.1.3 树的基本术语 • 结点的度和树的度 结点的度:结点具有的子树数或者后继结点个数。 树的度:树中所有结点的度的最大值。 • 分支结点和叶子结点 叶子结点(终端结点):度为0的结点。 分支结点(非终端结点):度大于0的结点。每个结点的分支数就是该结点的度。 • 孩子结点、双亲结点和兄弟结点 每个结点的子树的根,或每个结点的后继,称为该结点的孩子、儿子或子女。该结点被称为孩子结点的双亲、父亲或父母。具有同一双亲的孩子互称兄弟。一个结点的所有子树中的结点被称为该结点的子孙。一个结点的祖先定义为从树根结点到达该结点的路径上经过的所有结点。
A B C D F E G H I DS 5.1.3 树的基本术语 • 结点的层数和树的深度 结点的层数:从树根开始为第1层,它的孩子结点为第2层,以此类推。 树的深度(高度):树中所有结点的最大层数。 • 有序树和无序树 若树中各结点的子树是按照一定的次序从左向右安排的,则称之为有序树,否则称之为无序树。 • 森林 森林是m(m≥0)棵互不相交的树的集合。
DS 5.1.4 树的性质 • 【性质1】树中的结点数等于所有结点的度数加1。 • 【性质2】度为k的树中第i层上至多有ki-1个结点。 • 【性质3】深度为h的k叉树至多有(kh-1)/(k-1)个结点。 • 【性质4】具有n个结点的k叉树的最小深度为「logk(n(k-1)+1)」
DS 5.2 二叉树 • 5.2.1 二叉树的定义 • 5.2.2 二叉树的性质 • 5.2.3 二叉树的抽象数据类型 • 5.2.4 二叉树的存储结构
DS 5.2.1 二叉树的定义 • 二叉树(Binary Tree)是指树的度为2的有序树。 • 二叉树的递归定义为:二叉树或者是一棵空树,或者是一棵由一个根结点和两棵互不相交的分别称做根的左子树和右子树所组成的非空树,左子树和右子树又同样都是一棵二叉树。 二叉树BT,它由根结点A和左子树BT1,右子树BT2所组成,BT1和BT2 也是一棵二叉树。 左子树的根叫做左孩子,右子树的根叫做右孩子。
DS 5.2.2 二叉树的性质 • 【性质1】二叉树上终端结点数等于双分支结点数加1。 • 【性质2】二叉树上第i层上至多有2i-1个结点(i≥1)。 • 【性质3】深度为h的二叉树至多具有2h-1个结点。 在一棵二叉树中,当第i层的结点数为2i-1个时,则称此层的结点数是满的,当树中的每一层都满时,则称此树为满二叉树。 在一棵二叉树中,除最后一层外,若其余层都是满的,并且最后一层或者是满的,或者是在右边缺少连续若干个结点,则称此树为完全二叉树。
1 2 3 1 4 5 2 3 4 5 6 7 6 7 8 9 10 11 12 13 14 15 1 1 2 3 2 3 4 5 6 7 6 4 5 8 9 10 11 12 DS
DS 5.2.2 二叉树的性质 • 【性质4】对完全二叉树中编号为i的结点有如下性质: (1)若编号为i的结点有左孩子,则左孩子结点的编号的2i;若编号为i的结点有右孩子,则右孩子结点的编号为2i+1. (2)除树根结点外,若一个结点的编号为i,则它的双亲结点的编号为i/2,也就是,当i为偶数时,其双亲结点的编号为i/2,它是双亲结点的左孩子,当i为奇数时,其双亲结点的编号为(i-1)/2,它是双亲结点的右孩子。 (3)若i≤ n/2,即2i≤n,则编号为i的结点为分支结点,否则为叶子结点。 (4)若n为奇数,则每个分支结点既有左孩子,又有右孩子;若n为偶数,则编号最大的分支结点(编号为n/2)只有左孩子,没有右孩子,其余分支结点左、右孩子都有。 • 【性质5】具有n个结点的完全二叉树的深度为「lb(n+1)」或 lbn+1。
1 2 3 4 5 6 7 1 2 3 6 4 5 DS 5.2.2 二叉树的性质 • 在一棵二叉树中,若除最后一层外,其余层都是满的,而最后一层上的结点可以任意分布,则称此树为理想平衡二叉树,简称理想平衡树或理想二叉树。 理想平衡树 普通二叉树
DS 5.2.3 二叉树的抽象数据类型 • 二叉树的抽象数据类型包括数据和操作两个部分。 • 数据部分为一棵二叉树。 • 操作部分包括初始化二叉树、建立二叉树、遍历二叉树、查找二叉树、输出二叉树和清除二叉树等一些常用操作。
DS ADT BinaryTree is Data: 采用任一种方式存储的一棵二叉树,假定用标识符BT表示。 Operations void InitBTree(&BT); void CreateBTree(&BT, char* a); bool EmptyBTree(&BT); void TraverseBTree(&BT); bool FindBTree(&BT, &item); int BTreeDepth(&BT); void PrintBTree(&BT); void ClearBTree(BTreeType& BT); end BinaryTree
DS 5.2.4 二叉树的存储结构 • 1、顺序存储结构 首先对二叉树中的每个结点进行编号,然后以各结点的编号为下标,把各结点的值对应存储到一个一维数组。每个结点的编号与等深度的满二叉树中对应结点的编号相同,即树根结点编号为1,接着按照从上向下和从左向右的次序,若一个结点的编号为i,则左、右孩子的编号分别为2i和2i+1。
DS 优点:(1)各结点之间的关系比较容易表示。 (2)对于完全二叉树能充分利用存储空间。 缺点:对于单分支结点较多的二叉树不合适,空间浪费严重。
DS 5.2.4 二叉树的存储结构 • 2、链接存储结构 (1)二叉链表 在每个结点中设置三个域:值域、左指针域和右指针域,其结点结构为: 其中data表示值域,用来存储对应的数据元素,left和right分别表示左指针域和右指针域,用来分别存储左孩子和右孩子结点的存贮位置(即指针) 。 结点类型定义:struct BTreeNode { ElemType data; BTreeNode* left; BTreeNode* right; };
DS 空指针个数:2*n0+1*n1+0*n2 =2n0+n1 =n0+n1+n0 =n0+n1+n2+1 =n+1
DS 5.2.4 二叉树的存储结构 • 2、链接存储结构 (2)三叉链表 在上面的结点结构中再增加一个parent指针域,用来指向其双亲结点。这种存储结构既便于查找孩子结点,也便于查找双亲结点,当然也带来存储空间的相应增加。 结点类型定义:struct BTreeNode { ElemType data; BTreeNode* left; BTreeNode* right; BTreeNode* parent };
D A B C G E F A B C D E F G DS ^ ^ ^ ^ ^ ^ ^ ^ ^
D L R LDR、LRD、DLR RDL、RLD、DRL DS 5.3 二叉树的遍历 • 定义:指按照一定次序访问树中所有结点,并且每个结点的值仅被访问一次的过程。 • 根据二叉树的递归定义,一棵非空二叉树由根结点、左子树和右子树所组成。
DS • 方法 • 先序遍历:先访问根结点,然后分别先序遍历左子树、右子树 • 中序遍历:先中序遍历左子树,然后访问根结点,最后中序遍历右子树 • 后序遍历:先后序遍历左、右子树,然后访问根结点 • 按层次遍历:从上到下、从左到右访问各结点
DS 5.3 二叉树的遍历 1. 前序遍历算法 void PreOrder(BTreeNode* BT) { if(BT!=NULL) { cout<<BT->data<<' '; //访问根结点 PreOrder(BT->left); //前序遍历左子树 PreOrder(BT->right); //前序遍历右子树 } }
DS 5.3 二叉树的遍历 2. 中序遍历算法 void InOrder(BTreeNode* BT) { if(BT!=NULL) { InOrder(BT->left); //中序遍历左子树 cout<<BT->data<<' '; //访问根结点 InOrder(BT->right); //中序遍历右子树 } }
DS 5.3 二叉树的遍历 3. 后序遍历算法 void PostOrder(BTreeNode* BT) { if(BT!=NULL) { PostOrder(BT->left); //后序遍历左子树 PostOrder(BT->right); //后序遍历右子树 cout<<BT->data<<' '; //访问根结点 } }
A C B D A D L R D L R > > > B C D DS 先序遍历: D L R 先序遍历序列:A B D C
A C B D B L D R L D R > > > A C D DS 中序遍历: L D R 中序遍历序列:B D A C
A C B D L R D L R D B > > > A C D DS 后序遍历: L R D 后序遍历序列: D B C A
- + / a * f e b - c d DS - + a * b - c d / e f 先序遍历: a + b * c - d - e / f 中序遍历: a b c d - * + e f / - 后序遍历: 层次遍历: - + / a * e f b - c d
DS 例:已知一棵二叉树的先序遍历序列为:eadcbjfghi;中序遍历序列为 abcdjefhgi;画出二叉树的树形图,并求出它的后序遍历序列。 解题思路:1)根据先序(后序)可以确 定树(子树)的根结点 2)根据中序可以确定哪些结 点在左子树哪些结点在右 子树上
DS 5.4 二叉树的其他运算 1. 初始化二叉树 void InitBTree(BTreeNode*& BT) //初始化二叉树,即把树根指针置空 { BT=NULL; }
DS 5.4 二叉树的其他运算 2. 建立二叉树 基本思路:从保存二叉树的广义表的字符串a中输入每个字符: • 空格:不进行任何操作; • 字母:表明是结点的值,应先建立新结点,并把该结点作为左孩子(k=1)或右孩子(k=2)链接到双亲结点上; • 左括号:表明子表开始,把指向它前面字母所在结点的指针进栈,以便括号内的孩子结点向双亲结点链接之用,k置1; • 右括号:表明子表结束,退栈; • 逗号:表明以左孩子为根的子树处理完毕,应接着处理以右孩子为根的子树,k置2;
DS 5.4 二叉树的其他运算 2. 建立二叉树 Void createBTree(BTreeNode*& BT,char *a) {const int MaxSize=10; BTreeNode *S[MaxSize]; int top=-1; BT=NULL; BTreeNode *p; int k;
DS 2. 建立二叉树 int i=0; While (a[i]) { Switch (a[i]) { case ‘ ‘ : break; case ‘(‘: if (top==MaxSize-1) { cout<<“栈空间太小,请增加MaxSize的值!”<<endl; exit(1); } top++; s[top]=p; k=1; break; case ‘)’: if (top==-1) { cout<<“二叉树广义表字符串错!”<<endl; exit(1); } top--; break; case ‘,’: k=2; break;
DS 2. 建立二叉树 default: p=new BTreeNode; p->data=a[i]; p->left=p->right=NULL; if (BT==NULL) BT=p; else { if (k==1) s[top]->left=p; else s[top]->right=p;} } i++; }}
DS 5.4 二叉树的其他运算 3. 检查二叉树是否为空 Void EmptyBTree(BTreeNode* BT) { return BT==NULL; }
DS 5.4 二叉树的其他运算 4.求二叉树深度 若一棵二叉树为空,则它的深度为0,否则它的深度等于左子树和右子树中的最大深度加1。设dep1为左子树的深度,dep2为右子树的深度,则二叉树的深度为: max(dep1, dep2)+1
DS 求二叉树深度的递归算法如下: int DepthBTree(BTreeNode* BT) { if (BT==NULL) return 0; else { int dep1=DepthBTree(BT->left); //计算左子树的深度 int dep2=DepthBTree(BT->right); //计算右子树的深度 if(dep1>dep2) //返回树的深度 return dep1+1; else return dep2+1; }}
DS 5.4 二叉树的其他运算 5.从二叉树中查找值为x的结点,若存在则由x带回完整值并返回真,否则返回假 该算法类似于前序遍历,若树为空则返回false结束递归,若树根结点的值就等于x的值,则把结点值赋给x后并返回true结束递归,否则先向左子树查找,若找到则返回true结束递归,否则再向右子树查找,若找到则返回true结束递归,若左、右子树均未找到则返回false结束递归。
DS 具体算法如下: bool FindBTree(BTreeNode* BT, ElemType& x) { if(BT==NULL) return false; //树为空返回假 else { if(BT->data==x) { //树根结点的值等于x则由x带回结点值并返回真 x=BT->data; return true; } else { //向左子树查找若成功则继续返回真 if(FindBTree(BT->left,x)) return true; //向右子树查找若成功则继续返回真 if(FindBTree(BT->right,x)) return true; //左、右子树查找均失败则返回假 return false; } }}
DS 5.4 二叉树的其他运算 6.输出二叉树 广义表表示一棵二叉树的规则是:根结点被放在由左、右子树组成的表的前面,而表是用一对圆括号括起来的。 用广义表的形式输出一棵二叉树时,应首先输出根结点,然后再依次输出它的左子树和右子树,不过在输出左子树之前要打印出左括号,在输出右子树之后要打印出右括号;另外,依次输出的左、右子树要至少有一个不为空,若均为空就不用输出它们了。
DS 具体算法如下: void PrintBTree(BTreeNode* BT) { //输出二叉树的广义表表示 if(BT!=NULL) { //树为空时结束递归,否则执行如下操作 cout<<BT->data; //输出根结点的值 if(BT->left!=NULL || BT->right!=NULL) { cout<<'('; //输出左括号 PrintBTree(BT->left); //输出左子树 if(BT->right!=NULL) cout<<','; //若右子树不为空则首先输出逗号分隔符 PrintBTree(BT->right); //输出右子树 cout<<')'; //输出右括号 } } }