树的基本概念
定义
树是 n(n>=0)个结点的有限集。当 n=0 时,称为空树。
在任意一棵非空树中应满足:
- 有且仅有一个特定的称为根的节点。
- 当 n>1 时,其余节点可分为 m(m>0)个互不相交的有限集,每个集合本身又是一棵树,称为根的子树。
- 树的根结点没有前驱,除根结点外的所有结点有且只有一个前驱。
- 树中的所有结点都可以有零个或多个后继。
基本术语
结点与结点之间的关系
祖先:一个结点如果有后继,那么这个结点对于它的后继、后继的后继、后继的后继的后继、后继的后继的后继的后继…来说,是它们的祖先。
子孙:与祖先相对应,如果一个结点有前驱,那么这个结点对于它的前驱、前驱的前驱、前驱的前驱的前驱…来说,是它们的子孙。
双亲:双亲是特殊的祖先,一个结点对于它的后继结点来说,是它后继结点的双亲。
孩子:与双亲对应,一个结点对于它的前驱结点来说,是它前驱结点的孩子。
兄弟:一个结点对于其双亲的其他孩子来说,它们是兄弟。
堂兄弟:一个结点对于其双亲的兄弟的孩子来说,它们是堂兄弟。
基本概念
结点的度:一个结点的孩子的个数称为该结点的度。
树的度:孩子最多的结点的度称为树的度。
分支结点:至少有一个孩子的结点称为分支结点。
叶结点:没有孩子的结点称为叶结点。
结点的深度:对于根结点来说,一个结点是第几代子孙,那么该结点的深度就为多少。(例如,根节点是第 1 代,有一个结点相对于根结点来说是第三代,那么,该结点的深度为 3)
树的深度(高度):最后一代子孙结点的深度为树的深度。
有序树:兄弟之间的位序存在先后关系的树称为有序树(长幼有序)。
无序树:兄弟之间的位序不存在先后关系的树称为无序树。
路径:路径是指从一个结点到另一个结点的之间所经过的所有结点构成的序列。
路径长度:路径长度是路径上所经过的边的个数
森林:森林是 m(m>=0)个互不相交的树构成的集合。
举个 🌰
R 是 A 的祖先,R 和 A 是 D、E 的祖先。
R 是 A 的双亲,A 是 R 的孩子,D、E 是 A 的孩子,A 是 D、E 的双亲
D、E 是兄弟;D、E 和 F 是堂兄弟。
有两个孩子,A 的度是 2;R有3个孩子,R 的度是 3。
树中最大的度为 3,该树的度为 3。
G、H、I 是第 4 代子孙,深度为 4,D、E 是第 3 代子孙,D、E 的深度为 3。
树中最大的深度为 4,树的深度为 4
如果 D 和 E 可以互换位置,那么该树是无序树。
如果 D 和 E 不可以互换位置,那么该树是有序树。
R 到 D 的路径是(R、A、D),经过两条边,路径的长度是 2。
如果去掉 R 结点,那么 A 子树、B 子树、C 子树构成了一个森林。
性质
-
树的节点数 n 等于所有结点的度数+1。
理由:除了根结点外,其他的所有结点都可以被称为孩子结点,而结点的度=孩子的个数,所以结点的度数之和=所有孩子结点个数,加上 1 个根结点=树的所有结点的个数。
-
度为 m 的树中第 i 层上至多有 mi-1个结点(i>= 1)。
理由:由数学归纳法,第 1 层至多有 1 个结点 = m0个,第 2 层至多有 m 个=m1,第 3 层至多有 m2 个,所以第 i 层至多有 mi-1个
-
高度为 h 的 m 叉树至多有(mh-1)/(m-1)个结点。
理由:1+m+m2+…+mh-1=(mh-1)/(m-1)
-
度为 m、具有 n 个结点的树的最小高度 h = ⌈ l o g m ( n ( m + 1 ) + 1 ) ⌉ \lceil log_{m}(n(m+1)+1) \rceil ⌈logm(n(m+1)+1)⌉。
理由:为使高度最小,则从第1层到h-1层中,每层的结点数最多。有性质3可知,前h-1层的结点总数至多为 ( m h − 1 − 1 ) / ( m − 1 ) (m^{h-1}-1)/(m-1) (mh−1−1)/(m−1),第i层到第h层至多为 ( m h − 1 ) / ( m − 1 ) (m^{h}-1)/(m-1) (mh−1)/(m−1),所以 ( m h − 1 − 1 ) / ( m − 1 ) ≤ n ≤ ( m h − 1 ) / ( m − 1 ) (m^{h-1}-1)/(m-1) \leq n \leq (m^{h}-1)/(m-1) (mh−1−1)/(m−1)≤n≤(mh−1)/(m−1),有 h − 1 ≤ l o g m ( n ( m + 1 ) + 1 ) ≤ h h-1 \leq log_{m}(n(m+1)+1) \leq h h−1≤logm(n(m+1)+1)≤h,所以 h m i n = ⌈ l o g m ( n ( m + 1 ) + 1 ) ⌉ h_{min}=\lceil log_{m}(n(m+1)+1) \rceil hmin=⌈logm(n(m+1)+1)⌉
-
度为m,具有 n 个结点的树的最大高度h为 n - m + 1。
理由:为使树的高度最大,因此每一层仅有一个结点。而度为m,则至少有一个度为m的结点,即有m个孩子处于同一层,所有树的最大高度为 n - m + 1。
树的存储结构
树的存储方式有很多,但无论采用哪种存储方式,都必须要求能够唯一地反映树中各结点之间的逻辑关系,这里给出3种常用的存储结构。
双亲表示法
双亲表示法采用一组连续空间来存储每个结点,同时增设一个伪指针,指向其双亲结点。
这种方式利用每个节点只有唯一双亲的性质,可以很快地得到每个节点的双亲结点,但求结点的孩子需要遍历整个结构。
// C++
// 树的结点
template <class T>
class TreeNode{
public:
T data; // 存储的数据
int parent; // 双亲结点指针
}
// 树
template <class T>
class Tree{
public:
TreeNode<T> *nodes; // 结点数组
int n; // 结点个数
}
孩子表示法
孩子表示法是将每个结点的孩子结点视为一个线性表,且以单链表作为存储结构,则n个结点就有n个孩子链表(叶结点的孩子链表为空表)。而n个头指针有组成一个线性表,为便于查找,可采用顺序存储结构。
与双亲表示法相反,孩子表示法寻找孩子的操作非常方便,但寻找双亲的操作需要遍历n个结点中孩子链表指针域所指向的n个孩子链表。
//C++
#include <list>
template <class T>
class TreeNode{
public:
std::list<T> child; // 孩子链表
}
template <class T>
class Tree{
public:
TreeNode<T> *nodes; // 结点线性表
}
孩子兄弟表示法
孩子兄弟表示法有称为二叉树表示法,即以二叉链表作为树的存储结构。孩子兄弟表示法使每个结点包括三部分内容:结点值、指向结点第一个孩子结点的指针,以及指向结点下一个兄弟结点的指针。
孩子兄弟表示法比较灵活,其最大的优点是可以方便地实现树转化为二叉树的操作,易于查找结点的孩子等,但缺点是从当前结点查找其双亲结点比较麻烦。(可以为每个结点增设一个parent指针指向父节点来解决)
这里的图不是很恰当,结点右边的指针指向的兄弟,兄弟结点应该处于同一层,而不是上下层。(这里的图是基于二叉树来绘制的)。
// C++
template <class T>
class TreeNode{
public:
T data;
TreeNode<T> *firstChild, *nextSibling; // 第一个孩子以及右兄弟。
}
template <class T>
class Tree{
public:
TreeNode<T> *head; // 头指针。
}
如果你感兴趣的话,可以自己实现一下这些内容。
全篇至此结束,感谢阅读。