首页/数据结构/04-tree/线索二叉树 🔗 在 Obsidian 中打开
数据结构 · 04-tree

线索二叉树

重要度 ⭐⭐⭐⭐ 二叉树线索化前驱后继
速查
ltag/rtag:0 指向孩子,1 指向前驱/后继。中序线索二叉树找后继:若 rtag=1 则 rchild 是后继;否则是右子树中最左下结点。线索化 O(n),遍历不需栈空间 O(1)。

核心概念

普通二叉链表中有 n+1 个空指针域,线索二叉树利用这些空指针存放遍历序列中的前驱和后继信息

  • 前驱:遍历序列中某结点的前一个结点
  • 后继:遍历序列中某结点的后一个结点

线索化规则

  • 若左子树为空($lchild == NULL$),令 lchild 指向遍历序列中的前驱
  • 若右子树为空($rchild == NULL$),令 rchild 指向遍历序列中的后继
typedef struct ThreadNode {
    ElemType data;
    struct ThreadNode *lchild, *rchild;
    int ltag, rtag;   // 0:指向孩子;1:指向前驱/后继
} ThreadNode, *ThreadTree;
类型前驱/后继依据
先序线索二叉树先序遍历序列
中序线索二叉树中序遍历序列(最常考
后序线索二叉树后序遍历序列

中序线索化

ThreadTree pre = NULL;   // 全局变量,记录前驱

void InThread(ThreadTree T) {
    if (T != NULL) {
        InThread(T->lchild);            // 线索化左子树
        if (T->lchild == NULL) { T->ltag = 1; T->lchild = pre; }
        if (pre != NULL && pre->rchild == NULL) {
            pre->rtag = 1; pre->rchild = T;
        }
        pre = T;
        InThread(T->rchild);            // 线索化右子树
    }
}
void CreateInThread(ThreadTree T) {
    pre = NULL;
    if (T != NULL) { InThread(T); pre->rchild = NULL; pre->rtag = 1; }
}
         A
       /   \
      B     C
     / \     \
    D   E     F
中序序列:D B E A C F
线索化后:
D: lchild→NULL(无前驱), rchild→B(后继)
B: lchild→D(前驱), rchild→E(后继)
E: lchild→B(前驱), rchild→A(后继)
A: lchild→E(前驱), rchild→C(后继)
C: lchild→A(前驱), rchild→F(后继)
F: lchild→C(前驱), rchild→NULL(无后继)

线索二叉树遍历

// 中序遍历第一个结点:沿左孩子到底
ThreadNode *FirstNode(ThreadNode *p){
    while (p->ltag == 0) p = p->lchild;
    return p;
}
// 找后继:rtag=1 直接返回;否则右子树最左下
ThreadNode *NextNode(ThreadNode *p){
    if (p->rtag == 1) return p->rchild;
    else return FirstNode(p->rchild);
}
// 中序遍历(无需栈)
void InOrderThread(ThreadTree T){
    for (ThreadNode *p = FirstNode(T); p != NULL; p = NextNode(p)) visit(p);
}
// 找前驱:ltag=1 直接返回;否则左子树最右下
ThreadNode *PreNode(ThreadNode *p){
    if (p->ltag == 1) return p->lchild;
    else return LastNode(p->lchild);
}

先序与后序线索化

void PreThread(ThreadTree T) {
    if (T != NULL) {
        if (T->lchild == NULL) { T->ltag = 1; T->lchild = pre; }
        if (pre != NULL && pre->rchild == NULL) { pre->rtag = 1; pre->rchild = T; }
        pre = T;
        if (T->ltag == 0) PreThread(T->lchild);   // 需判断 ltag 防循环
        PreThread(T->rchild);
    }
}
后序线索化后序线索二叉树找后继较复杂,需要栈或 parent 指针。

手算示例

例 1:中序线索化

         1
       /   \
      2     3
     / \
    4   5
中序序列:4 2 5 1 3
结点ltaglchildrtagrchild
41NULL(无前驱)12
20(孩子4)0(孩子5)
51211
10(孩子2)0(孩子3)
3111NULL(无后继)

例 2:中序遍历线索二叉树

FirstNode(1):沿左到 4,返回 4 → visit(4)
NextNode(4):rtag=1,返回 rchild=2 → visit(2)
NextNode(2):rtag=0,FirstNode(5)=5 → visit(5)
NextNode(5):rtag=1,返回 rchild=1 → visit(1)
NextNode(1):rtag=0,FirstNode(3)=3 → visit(3)
NextNode(3):rtag=1,rchild=NULL,结束
序列:4 2 5 1 3 ✓

常见考法

考法 1给出二叉树,画出中序线索化后的结果。
考法 2中序线索二叉树结点 p 的后继:若 $rtag=1$,rchild 就是后继;否则是右子树中最左下结点。
考法 3写出中序线索二叉树的遍历算法(无栈)。
考法 4先序、中序、后序线索化各有什么特点(先序防循环、后序找后继复杂)。

易错点

易错清单
  1. ltag/rtag 含义:0 指向孩子,1 指向前驱/后继(核心定义)。
  2. pre 的作用:记录上一个访问的结点。
  3. 先序线索化需防循环:处理左子树前判断 ltag。
  4. 后序线索化找后继最复杂:可能需要 parent 指针。
  5. 第一个结点:中序最左下,先序是根,后序最左下。

核心结论

操作时间复杂度
线索化O(n)
找前驱/后继O(1)(有线索)或 O(h)(无线索)
遍历O(n)
空间O(1)(不需要栈)
优势遍历不需要栈(空间 O(1));找前驱后继方便。

记忆卡片

ltag/rtag 含义?
0 指向孩子,1 指向前驱/后继。
中序线索中找 p 的后继?
rtag=1 则 rchild 是后继;否则是右子树中最左下结点。
哪种线索化最常考?
中序线索化。
遍历线索二叉树的空间?
O(1),不需要栈。

交互动画 · 中序线索化

橙虚线 = 新挂上的线索(前驱/后继) 4 2 5 1 3 中序序列 ∅ 无前驱 无后继 ∅ 1 2 3 4 5
中序序列 4,2,5,1,3 —— 为每个空指针挂线索
点「下一步」:空左指针→前驱,空右指针→后继
结点 4 无前驱(lchild→∅)、后继 2;5 前驱 2、后继 1;3 前驱 1、无后继。2、1 有孩子不挂线。

相关知识点

binary-tree-traversal binary-tree-storage

↑ 以上为站内 HTML 相对链接(纯网页可浏览);本页右上「在 Obsidian 中打开」跳回源笔记。