目录
前言
兄弟们,前面已经介绍了链表(单向和双向)、栈和队列,这些都是一对一的线性结构,可是在现实生活中还有很多一对多的情况需要处理,接下来我们就进入一个新的世界了,那就是树,这个就十分的厉害了,那么先从它们的概念开始讲起!
一、树的概念及其结构
1.1树的概念
树是一种 非线性 的数据结构,它是由 n ( n>=0 )个有限结点组成一个具有层次关系的集合。 把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。①有一个 特殊的结点,称为根结点, 根节点没有前驱结点②除根节点外, 其余结点被分成M(M>0)个互不相交的集合T1、T2、……、Tm ,其中每一个集合 Ti(1<= i <= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有 0 个或多个后继③除了根节点之外,每个节点有且仅有一个父节点④一棵n个节点的树有n-1条边
因此, 树是递归定义 的。注意: 树形结构中的子树不能有任何交集,否则它就不是树形结构![]()
1.2树的其他相关概念
节点的度
|
一个节点含有的子树的个数
称为该节点的度,如A,度为2,D为3
|
叶节点或终端节点
|
度为0
的节点称为叶节点,如H,I,J,E,F,G
|
非终端节点或分支节点
|
度不为0
的节点,如A,B,C,D
|
双亲节点或父节点
|
若一个节点含有子节点,则这个节点称为其子节点的父节点
,如A,B,C,D
|
孩子节点或子节点
|
一个节点含有的子树的根节点称为该节点的子节点
,如上,H是D的子节点
|
兄弟节点
|
具有相同父节点的节点互称为兄弟节点
,如,D和E互称为兄弟节点
|
树的度
|
一棵树中,各个节点中的最大值称为树的度
,如,D的度为3,是最大节点的度,所以该树的度为3
|
节点的层次
|
从根开始定义起,根为第1层,根的子节点为第2层
,如A是第一层,BC就是第二层,以此类推
|
树的高度或深度
|
树中节点的最大层次
,
如上树,高度为4
|
堂兄弟节点
|
双亲在同一层的节点互为堂兄弟,
如图中的B,C在同一层,那么E和F就可以称为堂兄弟
|
节点的祖先
|
从根到该节点所经分支上的所有节点
,如A是所有节点的祖先
|
子孙
|
以某节点为根的子树中任一节点都称为该节点的子孙,
如所有的节点都是A的子孙
|
森林 |
由m(m>0)棵互不相交的树的集合称为森林
|
1.3树的表示
树的结构相对于线性结构来说比较复杂,要存储起来就比较的麻烦了,既要保存值域,又要保存节点与节点之间的关系,所以在实际中树的存储结构有很多种表示方法,如双亲表示法,孩子表示法,孩子兄弟表示法以及孩子双亲表示法等等
双亲表示法:在每个节点中,设置一个指示器指示其双亲结点在数组中的位置
结构如下:
规定:因为根节点没有双亲,所以根结点位置附为-1
代码定义如下
#define MAX_SIZE 6 typedef int elemtype; typedef struct TreeNode //结点结构 { elemtype data; //结点数据 int parent; //双亲位置 }TreeNode; typedef struct //树的结构 { TreeNode node[MAX_SIZE]; //结点数组 int root; //根的位置 int n; //结点个数 }Tree;
孩子表示法:每个结点有多个指针域,其中每个指针指向一棵子树的根节点
struct TreeNode { int val; struct TreeNode* child1; struct TreeNode* child2; struct TreeNode* child3; //..... struct TreeNode* childn; };
为了简洁,我们放在一个指针数组里,但是呢,这需要根据树有多少度去定义多少个结点
//假设树的度为6 #define N 6 struct TreeNode { int val; struct TreeNode* childArry[N];//指针数组 };
又或者可以用顺序表来存储孩子
struct TreeNode { int val; Seqlist childsl; //顺序表存储孩子 };
总的来说,还是不好去控制的,所以最常使用的还是孩子兄弟表示法,下面来简单了解一下
孩子兄弟表示法:
我们都知道,任意一棵树,它的结点的第一个孩子如果存在就是唯一的,它的右兄弟如果存在也是为唯一的。因此,我们设置两个指针,分别指向该结点的第一个孩子和此结点的右兄弟。
结构定义代码如下
//左孩子右兄弟 struct TreeNode { int val; struct TreeNode* leftchild; struct TreeNode* rightbrother; };
这样的做法比其他的方式要容易控制了很多,就不需要考虑到树的度的问题,有几个连几个它给查找某个孩子的带来了方便,当然了,如果要找到双亲,那这个就有缺陷了。那怎么办呢?其实完全可以在增加一个parent指针域,这里就不细谈了
其实这个表示法最大的好处是它把一棵复杂的树变成了一棵二叉树,什么是二叉树呢?别急,后面的文章将会细谈
1.4树的在实际中的应用
一般树在实际中表示文件系统的目录树结构,如LInux系统
二、二叉树的概念及结构
2.1二叉树的概念
二叉树:一棵二叉树是结点的一个有限集合,该集合 :1. 或者为空2. 由一个根节点加上两棵别称为左子树和右子树的二叉树组成
从上图可以看出:1. 二叉树 不存在度大于2 的结点(不一定度为2)2. 二叉树的子树有左右之分,次序不能颠倒,因此 二叉树是有序树注意:对于任意的二叉树都是由以下几种情况复合而成的:![]()
2.2特殊的二叉树
①满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是 2^K-1,则它就是满二叉树。(每一层都是满的)如
②完全二叉树 :完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。 对于深度为K 的,有n个结点的二叉树, 当且仅当其每一个结点都与深度为K的满二叉树中,按照从上到下,从左到右,编号从1至n的结点一一对 应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树( 前K-1层是满的,最后一层不一定满,但是从左到右必须连续的 )。两点特性:①叶子节点只会在最后两层出现②当某个结点左右孩子为空或者右孩子为空时,后面所有结点孩子均为空
那么,问题就来了,看下图
很显然这不是,因为根据完全二叉树的特性以及定义可知,最后一层可以不满,但从左到右必须连续!![]()
![]()
2.3二叉树的性质
① 若规定根节点的层数为 1 ,则一棵非空二叉树的 第 i 层上最多有 2^(i-1) 个结点.( 如满二叉树 )② 若规定根节点的层数为 1 ,则 深度为 h 的二叉树的最大结点数是 2^h-1(如满二叉树)③ 对任何一棵二叉树 , 如果度为 0 其叶结n0 , 度为 2 的分支结点个数为n2 , 则有 n0=n2 + 1④ 若规定根节点的层数为 1 ,具有 n 个结点的满二叉树的深度 , h=log2(n+1)(ps: 是log 2为底, n+1 为对数 )
⑤. 对于具有 n 个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从 0 开始编号,则对 于序号为i 的结点有:1. 若 i>0 , i 位置节点的双亲序号: (i-1)/2 ; i=0 , i 为根节点编号,无双亲节点2. 若 2i+1<n ,左孩子序号: 2i+1 , 2i+1>=n 否则无左孩子3. 若 2i+2<n ,右孩子序号: 2i+2 , 2i+2>=n 否则无右孩子ps:对于上述的性质,大家可以将一棵二叉树画出来,就可以得到对应得性质了下面给几道简单例题加深理解1. 某二叉树共有 399 个结点,其中有 199 个度为 2 的结点,则该二叉树中的叶子结点数为( )A 不存在这样的二叉树B 200C 198D 199答案 :根据性质③得出n0=n2+1 ------>n0=200,故选B2. 在具有 2n 个结点的完全二叉树中,叶子结点个数为( )A nB n+1C n-1D n/2解析:n0代表度为0,n1代表度为1,n2代表度为2n0+n1+n2=2nn0=n2+1---->2n0+n1=2n+1因为度为1的结点那么0,要么1,而结果2n+1为奇数,2n0为偶数,只有偶数+奇数才得奇数所以n1=1-->n0=n选A3.一棵完全二叉树的节点数位为 531 个,那么这棵树的高度为( )A 11B 10C 8D 12答案:B4. 一个具有 767 个节点的完全二叉树,其叶子节点个数为()A 383B 384C 385D 386选B
三、二叉树的存储结构
普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而 完全二叉树更适合使用顺序结构存储 。现实中我们通常把 堆(一种二叉树) 使用顺序结构的数组来存储,要注意的是这里的堆和操作系统 虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。
从上图可知,父子结点的下标存在着一定的规律左孩子=父*2+1右孩子=父*2+2父=(左孩子-1)/2对于非完全二叉树来说,就不太适合采用顺序存储了,很难找到具体结点的位置
二叉链式存储:用链表来表示一棵二叉树,即用链来指示元素的逻辑关系,通常的方法就是孩子兄弟法, 一个结点有数据域、指向左孩子的指针以及指向右孩子的指针!具体内容,后期尽请期待