引言
原文链接:树的基本概念 希望点进去的小伙伴关注一下我的公众号哟,文末有二维码,谢谢!
这篇文章讲的是树的基本概念,二叉树在下一篇文章中讲。
树这种数据结构在生活中很常见,比如公司的组织结构图、文件系统等。当有大量数据时,链表的线性访问速度是最慢的,因此树这种结构有着极其重要的地位。
1、树的定义
线性结构属于一对一的结构,而树属于一对多的结构。
树是n(n>=0)个结点的有限集,n=0称为空树;并且在任意一颗非空树中:
-
有且仅有一个根结点
-
当n>1时,又能分为m个(m>0)个子树
下图就不满足树的定义,因为子树间存在交叉。
2、结点
结点的一些概念:
-
树的结点=1个数据元素+若干个指向子树的指针。
-
结点的度:结点拥有的子树个数,度=0为叶子结点。除根结点外度!=0的结点为分支结点或内部结点。
-
树的度:最大的结点的度。比如有三个结点,度分别为4,、5、6,那么树的度就是6。
-
孩子结点:一个结点有多个孩子结点
-
双亲结点:为什么不叫父或母?因为一个结点只有一个父结点,索性叫双亲结点。
-
兄弟结点:即两个结点有同一双亲。
-
祖先和子孙:这个就不解释了
结点的层次、堂兄弟(这个概念不怎么重要)如下图所示。最大层次称为树的深度(或高度)。
线性结构和树结构的区别:
3、树的存储结构
前面已经了解了树和结点的一些基本概念,并且已经知道了树是一对多的结果,那么这种一对多如何在计算机中存储呢?有如下表示法:
-
双亲表示法
-
孩子表示法
-
孩子兄弟表示法
3.1、双亲表示法
假设现在有这样一棵树,如下图所示。
那么根据双亲表示法,可以用如下数据表示这棵树的逻辑关系。
下标 | data | parent |
---|---|---|
0 |
A | -1 |
1 |
B | 0 |
2 |
C | 0 |
3 |
D | 1 |
4 |
E | 2 |
5 |
F | 2 |
6 |
G | 3 |
7 |
H | 3 |
8 |
I | 3 |
9 | J | 4 |
该表可以采用顺序存储结构,每一行对应一个结点,该结点存储两部分内容:结点值和双亲结点指针。
这种存储方式很容易找到某一结点的双亲结点,其时间复杂度为O(1),但是要找某一结点的所有孩子结点,必须要遍历整个表才行!
3.2、孩子表示法
即每个结点存储结点的数据和指向孩子的指针。
但是每个结点的度是不同的,那么孩子指针设置多少个合适呢?有以下几种方案:
-
统一设成最大值,即树的度,但这样会造成空间浪费,特别是树中的结点的度差别很大的时候。
-
孩子指针个数等于结点的度,但需要一个额外位置来存储该结点的孩子指针个数,但这种方案虽然避免了空间浪费,但需要额外维护一个数值。
上述两种方案都有缺点,根本原因在于每个结点的孩子指针是固定的,那将孩子指针设计成动态扩展的不就行了,因此可以采用数组+链表的设计方式,如下图所示。
当有n个结点时,数组的长度为n,并且每个数组元素都延伸了一条单向链表来存储它的孩子。
但是这种结构需要遍历整棵树才能知道某个结点的双亲,因此可以在每个数组元素中存储该结点的双亲,如下图所示。
3.3、孩子-兄弟表示法
前面两种表示法的思路是:要么存储结点的孩子,要么存储结点的双亲。而孩子-兄弟表示法,则存储的是结点的第一个孩子和结点右边的兄弟。
如果想查找某个结点的所有孩子,则首先可以迅速查找它的第一个孩子,然后根据第一个孩子,可以找到这个孩子右边的兄弟,依次类推,就可以找到该结点所有的孩子。
但是,如果想找到某个结点的双亲呢?如果有必要的话,可以对某个结点增加一个parent指针。
孩子-兄弟表示法如下图所示。
这种表示法有一个好处就是,因为每个结点除了数据之外,只存储孩子和兄弟两个指针,因此很容易将上图转化为一颗二叉树,如下图所示。即使是再复杂的树也能转化为二叉树。这样就可以利用二叉树的特性和算法来处理这棵树了。
觉得写得不错的小伙伴,扫码关注一下我的公众号吧,谢谢呀!