408 数据结构|线索二叉树两题详解:先序线索化后的空链域 + 中序前驱/后继判断

对应章节:数据结构 → 树与二叉树 → 线索二叉树

这两题真正考的是三个点:

  1. 二叉树为什么有 n+1 个空指针;

  2. 线索化时,空指针分别如何改造成"前驱线索"和"后继线索";

  3. "有线索"不等于"每个结点都能通过一根线索直接找到前驱和后继"。


一、做题前先建立统一模型

设结点结构为:

复制代码
[lchild] [data] [rchild]

在线索二叉树中,通常再增加两个标志位:

复制代码
ltag = 0:lchild 指向真正的左孩子
ltag = 1:lchild 是前驱线索

rtag = 0:rchild 指向真正的右孩子
rtag = 1:rchild 是后继线索

也就是说:

复制代码
原来为空的左指针  → 可以改成"前驱线索"
原来为空的右指针  → 可以改成"后继线索"

注意:只有原来为空的指针域才能拿来做线索。

如果某个结点本来就有左孩子或右孩子,那么对应的指针域仍然必须指向孩子,不能同时拿来存线索。


二、为什么 n 个结点的二叉树有 n+1 个空指针?

每个结点有两个孩子指针,因此共有:

复制代码
2n 个指针域

而一棵有 n 个结点的二叉树共有:

复制代码
n - 1 条边

每条边对应一个非空孩子指针,因此非空指针有 n-1 个。

所以空指针数为:

复制代码
2n - (n - 1)
= n + 1

因此:

n 个结点的二叉树,一共有 n+1 个空链域。

线索二叉树正是利用这些空链域来保存遍历序列中的前驱、后继信息。


第 25 题

题目

一棵左子树为空 的二叉树在先序线索化后,其中空的链域的个数是( )。

选项:

复制代码
A. 不确定
B. 0 个
C. 1 个
D. 2 个

答案

D:2 个。


1. 先抓住"先序遍历"的第一个结点

先序遍历顺序:

复制代码
根 → 左 → 右

所以整棵树先序遍历的第一个结点一定是根结点

题目又告诉我们:

复制代码
根的左子树为空

因此根结点的左指针本来就是空的。

画出来:

复制代码
        A
       / \
    NULL  右子树

由于 A 是先序遍历的第一个结点,所以:

复制代码
A 没有前驱

而 A 的左指针又恰好是空指针,本来应该用来存"前驱线索",但它没有前驱,因此:

复制代码
A.lchild = NULL

所以这里已经确定有 1 个空链域


2. 再看先序遍历的最后一个结点

设先序遍历序列为:

复制代码
A → ... → X

其中 X 是最后访问的结点。

X 不可能还有孩子。

因为如果 X 还有左孩子或右孩子,那么按照先序遍历,访问完 X 之后还必须继续访问它的孩子,X 就不可能是最后一个结点。

所以:

复制代码
最后一个结点 X 一定是叶子结点

也就是:

复制代码
       X
      / \
   NULL NULL

X 是整个先序序列的最后一个结点,因此:

复制代码
X 没有后继

X 的右指针本来为空,本应改造成"后继线索",但它没有后继,所以:

复制代码
X.rchild = NULL

这又产生 1 个空链域


3. 所以一共是两个

复制代码
第一个结点(根):
左指针 → 没有前驱 → NULL

最后一个结点:
右指针 → 没有后继 → NULL

因此:

复制代码
空链域总数 = 1 + 1 = 2

答案:

复制代码
D. 2 个

4. 用具体例子看最直观

假设原树是:

复制代码
        A
       / \
    NULL  B
         / \
        C   D

先序遍历:

复制代码
A → B → C → D

线索化之后:

复制代码
A:
左边原本为空
A 又没有前驱
所以 A.left = NULL


C:
C 没有左右孩子
先序前驱是 B,后继是 D

C.left  → B
C.right → D


D:
D 是先序最后一个结点
D 没有后继
所以 D.right = NULL

最终仍然为空的是:

复制代码
A.left  = NULL
D.right = NULL

正好 2 个


5. 一个特殊情况也不影响答案

如果整棵树只有一个根结点:

复制代码
    A
   / \
NULL NULL

先序序列只有:

复制代码
A

A 同时:

复制代码
没有前驱
没有后继

于是:

复制代码
A.left  = NULL
A.right = NULL

仍然是 2 个空链域


第 26 题

题目

在线索二叉树中,下列说法不正确的是( )。

选项:

复制代码
A. 在中序线索树中,若某结点有右孩子,
   则其后继结点是它的右子树的最左下结点。

B. 在中序线索树中,若某结点有左孩子,
   则其前驱结点是它的左子树的最右下结点。

C. 线索二叉树是利用二叉树的 n+1 个空指针
   来存放结点的前驱和后继信息的。

D. 每个结点通过线索都可以直接找到它的前驱和后继。

答案

D 错误。


A 为什么正确?

中序遍历顺序:

复制代码
左子树 → 根 → 右子树

假设当前结点为 A,而且 A 有右孩子:

复制代码
        A
         \
          B
         /
        C
       /
      D

访问完 A 之后,下一步进入 A 的右子树。

但右子树中第一个被中序访问的结点不是一定是 B,而是:

复制代码
右子树中最左边的结点

在上图中:

复制代码
A 的中序后继 = D

所以:

若某结点有右孩子,它的中序后继是右子树中最左下的结点

因此 A 正确。


B 为什么正确?

同理,中序遍历中:

复制代码
左子树 → 根 → 右子树

如果 A 有左孩子,那么在访问 A 之前,必须先访问完整个左子树。

左子树中最后被中序访问的结点就是:

复制代码
左子树中最右边的结点

例如:

复制代码
          A
         /
        B
         \
          C
           \
            D

则:

复制代码
A 的中序前驱 = D

所以:

若某结点有左孩子,它的中序前驱是左子树中最右下的结点

因此 B 正确。


C 为什么正确?

前面已经证明:

复制代码
n 个结点
→ 一共有 2n 个孩子指针域

实际边数
→ n - 1

空指针数
→ 2n - (n - 1)
→ n + 1

这些空指针正好可以利用起来,存放遍历序列中的前驱和后继信息。

因此 C 正确。


D 为什么错误?

D 的说法是:

复制代码
每个结点通过线索都可以直接找到前驱和后继

问题出在两个字:

复制代码
"每个"
"直接"

线索只存在于原本为空的孩子指针域中。

如果某个结点本来有左孩子:

复制代码
ltag = 0

那么:

复制代码
lchild 指向左孩子

它就不是前驱线索。

此时想找这个结点的中序前驱,不能直接顺着 lchild 就得到答案,而是要:

复制代码
先进入左子树
再不断向右
找到左子树中最右边的结点

同理,如果某结点本来有右孩子:

复制代码
rtag = 0

那么 rchild 是真正的右孩子,不是后继线索。

要寻找中序后继,需要:

复制代码
先进入右子树
再不断向左
找到右子树中最左边的结点

所以并不是每个结点都可以靠"一根线索"直接找到前驱和后继。


第 26 题最关键的图

对于中序线索树,找前驱和后继可以这样记:

复制代码
                当前结点 P
                 /      \
                /        \
          有左孩子      有右孩子
              |             |
              v             v
     左子树一直向右     右子树一直向左
              |             |
              v             v
          中序前驱        中序后继

如果本来没有左孩子:

复制代码
ltag = 1
lchild 直接就是前驱线索

如果本来没有右孩子:

复制代码
rtag = 1
rchild 直接就是后继线索

所以完整模型是:

复制代码
找中序前驱:

① ltag = 1
   → lchild 就是前驱,直接得到

② ltag = 0
   → 有左子树
   → 进入左子树
   → 一直向右
   → 最右结点是前驱

找中序后继:

① rtag = 1
   → rchild 就是后继,直接得到

② rtag = 0
   → 有右子树
   → 进入右子树
   → 一直向左
   → 最左结点是后继

这正是 D 错误的原因。


两题放在一起总结

必须记住的 4 句话

复制代码
1. n 个结点的二叉树有 n+1 个空指针。

2. 空左指针可改造成"前驱线索"。

3. 空右指针可改造成"后继线索"。

4. 有孩子时,对应指针仍然是孩子指针,
   因此不一定能通过线索直接得到前驱/后继。

中序前驱 / 后继口诀

复制代码
有左孩子:
前驱 = 左子树最右

有右孩子:
后继 = 右子树最左

无左孩子且已线索化:
左线索 = 前驱

无右孩子且已线索化:
右线索 = 后继

第 25 题识别信号

题目出现:

复制代码
先序线索化
+
根的左子树为空

立即想到:

复制代码
根是先序第一个结点
→ 没有前驱
→ 左指针又正好为空
→ 留下一个 NULL

最后一个先序结点
→ 没有后继
→ 右指针留下一个 NULL

总共 2 个

复盘点

复盘点 1:线索只能占用"空指针"

不要把线索理解成给每个结点额外增加两个前驱/后继指针。

线索化本质是:

复制代码
废物利用:把原本为空的孩子指针拿来存前驱/后继。

复盘点 2:"有线索二叉树"不代表前驱后继都能 O(1) 直接顺指针得到

如果对应方向本来有孩子,那么那个指针就是孩子指针,不是线索。

因此中序线索树中:

复制代码
有左孩子 → 找前驱要去左子树最右边
有右孩子 → 找后继要去右子树最左边

复盘点 3:第一个结点没有前驱,最后一个结点没有后继

任何遍历序列都可以先从这个角度判断:

复制代码
第一个结点:
前驱不存在

最后一个结点:
后继不存在

但"前驱/后继不存在"是否会形成一个真正的空链域,还要看这个结点对应的孩子指针本来是不是空的。

这就是第 25 题专门强调"左子树为空"的原因。

相关推荐
GeekZHR1 小时前
C语言指针进阶补充6:动态内存管理、mem系列内存函数、复杂指针声明,一次补齐指针的“三大盲区“
java·c语言·算法·指针
疯狂打码的少年2 小时前
【数据结构】二叉排序树(BST)的定义与操作
数据结构·笔记·算法
wabs6662 小时前
关于字符串【力扣541.反转字符串II的思考】
数据结构·算法·leetcode·字符串
caimouse3 小时前
ReactOS 图形系统分析(36):位块传输 — bitblt.c
c语言·reactos
Tyler_TXZ4 小时前
C++C语言之——树
c语言·c++·算法·
小羊先生car4 小时前
F429-HAL-Can总线(回环及双机实验)(2026/8/15)
c语言·单片机·嵌入式硬件·软件构建
旖旎夜光4 小时前
LeetCode 30:串联所有单词的子串(滑动窗口) —— 题解
数据结构·c++·算法·leetcode·滑动窗口
白狐_7984 小时前
408 数据结构|完全二叉树两道典型题:第 6 层叶结点数与叶结点总数
数据结构·算法
caimouse4 小时前
ReactOS 图形系统分析(38):区域子系统 — region.c
c语言