第5章 二叉树
树属于半线性结构(semi-linear structure):树其中的元素之间并不存在天然的直接后继或直接前驱关系。不过,只要附加某种约束(比如遍历),也可以在树中的元素之间确定某种线性次序,因此树属于半线性结构(semi-linear structure)。
随着从线性结构转入树结构,我们的思维方式也将有个飞跃;相应的,算法设计的策略与模式也会因此有所变化,许多基本的算法也将得以更加高效地实现。
作为树的特例,二叉树实际上并不失其一般性:无论就逻辑结构或算法功能而言,任何有根有序的多叉树,都可等价地转化并实现为二叉树。
5.1 二叉树及其表示
5.1.1 树
-
根:root
-
深度与层次:depth。
沿每个节点v到根r的唯一通路所经过边的数目,称作v的深度(depth),记作depth(v)
约定根节点的深度depth(r) = 0,即属于第0层
-
祖先、后代与子树:ancestor、descendant and subtree
任一节点v在通往树根沿途所经过的每个节点都是其祖先(ancestor),v是它们的后代(descendant)。特别地,v的祖先/后代包括其本身,而v本身以外的祖先/后代称作真祖先(proper ancestor)/真后代(proper descendant)。
特别地,若节点u是v的祖先 且恰好比v高出 一层, 则称u是v的父亲(parent),v是u的孩子(child)。
v的孩子总数,称作其度数或度(degree),记作deg(v)。无孩子的节点称作叶节点(leaf),包括根在内的其余节点皆为内部节点(internal node)。
v所有的后代及其之间的联边称作子树(subtree),记作subtree(v)。
-
高度:height
树T中所有节点深度的最大值称作该树的高度(height),记作height(T)。
树的高度总是由其中某一叶节点的深度确定的。特别地,本书约定,仅含单个节点的树高度为0,空树高度为-1。
推而广之,任一节点v所对应子树subtree(v)的高度,亦称作该节点的高度,记作height(v)。特别地,全树的高度亦即其根节点r的高度,height(T) = height(r)。
5.1.2 二叉树
- 二叉树(binary tree) 中每个节点的度数均不超过2。
- 因此在二叉树中,同一父节点的孩子都可以左、右相互区分——此时,亦称作有序二叉树(ordered binary tree)。
- 特别地,不含一度节点的二叉树称作真二叉树(proper binary tree)。
5.1.3 多叉树
一般地,树中各节点的孩子数目并不确定。每个节点的孩子均不超过k个的有根树,称作k叉树(k-ary tree)。
-
父节点:
将各节点组织为向量或列表,其中每个元素除保存节点本身的信息(node)外,还需要保存父节点(parent)的秩或位置。可为树根指定一个虚构的父节点-1或NULL,以便统一判断。
如此,所有向量或列表所占的空间总量为O(n),线性正比于节点总数n。时间方面,仅需常数时间,即可确定任一节点的父节点;但反过来,孩子节点的查找却不得不花费O(n)时间访遍所有节点。
-
孩子节点:
若注重孩子节点的快速定位,可如图5.4所示,令各节点将其所有的孩子组织为一个向量或列表。如此,对于拥有r个孩子的节点,可在O(r + 1)时间内列举出其所有的孩子(O(1)定位到节点,O(r)遍历所有孩子)。
-
父节点 + 孩子节点
以上父节点表示法和孩子节点表示法各有所长,但也各有所短。为综合二者的优势,消除缺点,可如图5.5所示令各节点既记录父节点,同时也维护一个序列以保存所有孩子。
尽管如此可以高效地兼顾对父节点和孩子的定位,但在节点插入与删除操作频繁的场合,为动态地维护和更新树的拓扑结构,不得不反复地遍历和调整一些节点所对应的孩子序列。然而,向量和列表等线性结构的此类操作都需耗费大量时间,势必影响到整体的效率。
-
有序多叉树 = 二叉树
解决上述难题的方法之一,就是采用支持高效动态调整的二叉树结构。为此,必须首先建立起从多叉树到二叉树的某种转换关系,并使得在此转换的意义下,任一多叉树都等价于某棵二叉树。当然,为了保证作为多叉树特例的二叉树有足够的能力表示任何一棵多叉树,我们只需给多叉树增加一项约束条件——同一节点的所有孩子之间必须具有某一线性次序。
仿照有序二叉树的定义,凡符合这一条件的多叉树也称作有序树(ordered tree)。幸运的是,这一附加条件在实际应用问题中往往自然满足。以互联网域名系统所对应的多叉树为例,其中同一域名下的分支通常即按照字典序排列。
-
长子 + 兄弟
由图5.6(a)的实例可见,有序多叉树中任一非叶节点都有唯一的“长子”,而从该“长子”出发,可按照预先约定或指定的次序遍历所有孩子节点。故可如图(b)所示,为每个节点设置两个指针,分别指向其“长子”和下一“兄弟” 。
若将这两个指针分别与二叉树节点的左、右孩子指针统一对应起来,则可进一步地将原有序多叉树转换为图(c)所示的常规二叉树。
在这里,一个饶有趣味的现象出现了:
尽管二叉树只是多叉树的一个子集,但其对应用问题的描述与刻画能力绝不低于后者。
就计算效率而言,二叉树也并不逊色于一般意义上的树。
得益于其定义的简洁性以及结构的规范性,二叉树所支撑的算法往往可以更好地得到描述,更加简捷地得到实现。
故二叉树的身影几乎出现在所有的应用领域当中。
“开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 8 天,点击查看活动详情”