二叉树

Mr.Hdd数据结构二叉树大约 20 分钟...

「二叉树 Binary Tree」

二叉树的定义及主要特征

二叉树是一种特殊的树型结构,其特点有:

  • 每个结点至多有两颗子树 ( 即不存在度大于 22 的结点 )
  • 二叉树的子树有左右之分,其次序是不能任意颠倒。( 若将二叉树的左、右子树颠倒,其会变为另一颗二叉树 )

区分二叉树与度为 2 的树

  • 二叉树并不是度为 22 的树,二叉树上的结点的度可以是 0,1,20,1,2

二叉树常用术语

  • 「根结点 Root Node」:二叉树最顶层的结点,其没有父结点;
  • 「叶结点 Leaf Node」:没有子结点的结点,其两个指针都指向 null
  • 结点所处「层 Level」:从顶置底依次增加,根结点所处层为 11
  • 结点「度 Degree」:结点的子结点数量,二叉树中度的范围是 0,1,20, 1, 2
  • 「边 Edge」:连接两个结点的边,即结点指针;
  • 二叉树「高度 、深度」:二叉树中根结点到最远叶结点走过的数量;(根 — 叶)
  • 结点「深度 Depth」 :根结点到该结点走过边的数量;(根 — 结点)
  • 结点「高度 Height」:最远叶结点到该结点走过边的数量;(结点 — 叶)

image-20221226171731561

高度和深度的定义

高度和深度的定义并不只有 “边” 的定义,也有以 “结点个数” 定义的,所以当以结点个数作为定义时,需要在以边为标准的基础上 + 1.

常见二叉树

完美二叉树(满二叉树)

​ 「完美二叉树 Perfect Binary Tree」的所有层的结点都被完全填满。在完美二叉树中,所有结点的度 = 2 ;若树高度 = hh ,则结点总数 = 2h+112^{h+1}-1 ,呈标准的指数级关系。

image-20221226170711039

完全二叉树

「完全二叉树 Complete Binary Tree」只有最底层的结点未被填满,且最底层结点尽量靠左填充。

image-20221226171138593

完全二叉树非常适合用数组来表示。如果按照层序遍历序列的顺序来存储,那么空结点 null 一定全部出现在序列的尾部,因此我们就可以不用存储这些 null 了。

完满二叉树

「完满二叉树 Full Binary Tree」除了叶结点之外,其余所有结点都有两个子结点。

image-20221226171646207

平衡二叉树

「平衡二叉树 Balanced Binary Tree」中任意结点的左子树和右子树的高度之差的绝对值 1≤1

image-20221226172224641

二叉树的退化

当二叉树的每层的结点都被填满时,达到「完美二叉树」;而当所有结点都偏向一边时,二叉树退化为「链表」。

  • 完美二叉树是一个二叉树的“最佳状态”,可以完全发挥出二叉树“分治”的优势;
  • 链表则是另一个极端,各项操作都变为线性操作,时间复杂度退化至 O(n)O(n)

image-20221226172820794

完美二叉树和退化为链表的二叉树的叶结点数量、结点总数、高度等达到极大或极小值。

完美二叉树链表
ii结点数量2i12^{i-1}11
树的高度为 hh叶结点的数量2h2^h11
树的高度为 hh结点的数量2h+112^{h+1}-1h+1h+1
树的结点总数为 nn 时的深度log2(n+1)1log_2(n+1)-1n1n-1

二叉树的基本特征

二叉树也是树,而树是 无环连通图 ,所以有 nn 个结点的树,其边的数量 B=n1B = n-1 .

  1. 非空二叉树上的叶子结点数等于度为 22 的结点数加 11 ,即 n0=n2+1n_0 = n_2 + 1.
  2. 对一颗具有 nn 个结点的完全二叉树中的结点从 11 开始按层序编号,则对于任意的编号为 i(1in)i(1≤i≤n) 的结点有:
    • 如果 i>1i>1 ,则结点 ii 的双亲的编号为 i2⌊\frac{i}{2}⌋ ;否则结点 ii 为根结点.
    • 如果 2in2i ≤ n ,则结点 ii 的左孩子的编号为 2i2i ,否则结点 ii 无左孩子.
    • 如果 2i+1n2i+1≤ n ,则结点 ii 的右孩子的编号为 2i+12i+1 ,否则结点 ii 无右孩子.
    • 结点所在的深度为 log2(i+1)+1⌊log_2(i+1)⌋+1 .
对性质的推导
  • 性质 1 的证明:

一颗二叉树的结点总数 n=n0+n1+n2n = n_0+n_1+n_2 ,边的总数 B=n1=n1+2n2B = n -1 = n_1 + 2n_2 ,所以得:n0+n1+n2=n1+2n2+1n_0+n_1+n_2=n_1+2n_2+1 . 即:n0=n2+1n_0=n_2+1.

二叉树的表示方式

1.数组表示

使用普通数组存储二叉树时,不能直接在结点中存储结点之间的关系,需要推导父结点索引与子结点索引的「映射公式」:设结点的索引为 ii ,则该结点的左子结点索引为 2i+12i+1 、右子结点索引为 2i+22i+2

例如数组存储满二叉树:

image-20221226211652090

但是满二叉树的并不常见,要想使用使用数组存储其他类型的二叉树,就必然存在 null 值,即需要将一颗不是满二叉树的二叉树用空结点填充为一颗满二叉树。如下图:

image-20221226212356195

对于完全二叉树来说,其虽然有空结点,但是由于使用层序遍历的顺序存储的原因,这些空结点都在数组的结尾,这使得可以直接不对其进行存储,所以使用数组表示完全二叉树更加合适。

image-20221226212749450


2.链表表示


二叉树的遍历

层序遍历

「层序遍历 Hierarchical-Order Traversal」从顶至底、一层一层地遍历二叉树,并在每层中按照从左到右的顺序访问结点。层序遍历本质上是「广度优先遍历 Breadth-First Traversal」.

层序遍历


前序、中序、后序遍历

相对地,前、中、后序遍历皆属于「深度优先遍历 Depth-First Traversal」,即每次搜索会直接搜索到叶子结点,然后再回溯去搜索其他的结点。

如下图所示,左侧是深度优先遍历的的示意图,右上方是对应的递归实现代码。深度优先遍历就像是绕着整个二叉树的外围“走”一圈,走的过程中,在每个结点都会遇到三个位置(即是向下继续搜索左子结点、右子结点或是回溯到父结点),分别对应前序遍历、中序遍历、后序遍历。

前序,中序,后序遍历

位置含义此处访问结点时对应
橙色圆圈处刚进入此结点,即将访问该结点的左子树前序遍历 Pre-Order Traversal
蓝色圆圈处已访问完左子树,即将访问右子树中序遍历 In-Order Traversal
紫色圆圈处已访问完左子树和右子树,即将返回后序遍历 Post-Order Traversal

由遍历序列确认二叉树

  • 前序 / 后序遍历和中序遍历可以唯一确定一颗二叉树
  • 前序遍历和后序遍历不能唯一确定一颗二叉树

image-20230101154717176

【求解一颗二叉树某一遍历顺序序列】

这三种序列都是从根结点开始的深搜序列,根据其选择的优先级顺序,例如前序遍历为 (根,左,右) (这是根,左,右是相对而言的,不是整颗树的),例如上图中二叉树的前序遍历思考过程如下:

  1. 首先是到 A (根)结点,是 “根” ,入序列,然后根据 (根,左,右) 原则遍历该 “根结点” 的 “左” 结点,
  2. 来到左结点 B , 是 “左” ,入序列,因为是深度优先,所以继续根据 (根,左,右) 原则向下遍历以 B 为 “根” 的子树,直至以 B 为 “根” 的子树遍历完毕,
  3. 对于 A (根)结点来说,(根,左) 已经遍历完毕,然后继续遍历 “右” ,即来到 E ,是 “右” ,入序列,然后继续前序遍历原则直接所有结点遍历结束.

【根据前序 / 后序遍历和中序遍历唯一确定一颗二叉树】

image-20221227191400186


「深度优先遍历 Depth-First Traversal」使用栈作为数据存储结构,递归中使用的系统栈,系统栈会使用后自己回溯,也可以模拟栈来完成遍历。

以中序遍历为例:

image-20221231175137232


后序遍历与前序遍历、中序遍历不同,后序遍历过程中,当一个结点作为 “根结点” 时,它的左子树遍历完之后不能出栈,必须等到右子树遍历完成之后才可以出栈,所以需要对当前结点的右子树是否遍历完成做出判断。


上述思路有些麻烦,原二叉树镜像之后的树的前序遍历的翻转正好是原树的后序遍历,如下图:

image-20230101155358861


线索二叉树

「线索二叉树 Thread Binary Tree」利用二叉树中未被利用的指针表示某种遍历序列中前驱和后序信息的二叉树。

一般的链表存储二叉树时只存储结点之间的父子关系,对于 nn 个结点的二叉树来说,每个结点都有左右两个指针,共有 2n2n 个,而父子关系 (二叉树中的边) 共有 n1n-1 ,所以还剩 n+1n+1 的指针没有表示任何关系 (未被利用),所以引入线索二叉树的原因是加快查找某种遍历序列下结点的前驱和后继,从而通过某种遍历顺序下结点的前驱和后继直接进行某种顺序的遍历。(若不使用线索二叉树直接存储的话,每次找一个结点的前驱或者后继的时候,都需要重新进行一次遍历,这样效率很低)

线索二叉树的结点表示信息如下:

image-20221227194004998

为线索二叉树的结点增加信息如下:


为二叉树增加一个全局前驱结点,用于线索化时处理前驱节点的后继结点


中序线索二叉树

中序线索二叉树

构造中序线索二叉树


使用构造好的中序线索二叉树对二叉树进行中序遍历、找前驱、找后继

中序线索二叉树找前驱和后继


前序线索二叉树

前序线索二叉树

构造前序线索二叉树


注意

注意点

使用构造好的前序线索二叉树对二叉树进行前序遍历、找前驱、找后继

在前序线索二叉树中寻找结点的前驱时需要使用到结点的父结点,所以对结点信息进行修改,同时需要在构建二叉树时初始化每个结点的父结点

详情

有了父结点就可以更加方便的寻找结点的前驱

找前序前驱


后序线索二叉树

构造后序线索二叉树


使用构造好的后序线索二叉树对二叉树进行后序遍历、找前驱、找后继

找后序后继

image-20230114222538798


你认为这篇文章怎么样?
  • 0
  • 0
  • 0
  • 0
  • 0
  • 0
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v2.14.9