首页/数据结构/04-tree/二叉树的遍历 🔗 在 Obsidian 中打开
数据结构 · 树与二叉树

二叉树的遍历

难度 ★★★重要度 ★★★★★ 考查频率 高 二叉树前序中序后序层序
速查
先序根左右、中序左根右、后序左右根、层序用队列。构造树:先序+中序 ✅后序+中序 ✅先序+后序 ❌。全部 $O(n)$,递归空间 $O(h)$。

一、四种遍历方式

1. 先序遍历(PreOrder)—— 根 → 左 → 右

void PreOrder(BiTree T) {
    if (T != NULL) {
        visit(T);              // 访问根结点
        PreOrder(T->lchild);   // 遍历左子树
        PreOrder(T->rchild);   // 遍历右子树
    }
}

2. 中序遍历(InOrder)—— 左 → 根 → 右

void InOrder(BiTree T) {
    if (T != NULL) {
        InOrder(T->lchild);    // 遍历左子树
        visit(T);              // 访问根结点
        InOrder(T->rchild);    // 遍历右子树
    }
}

3. 后序遍历(PostOrder)—— 左 → 右 → 根

void PostOrder(BiTree T) {
    if (T != NULL) {
        PostOrder(T->lchild);  // 遍历左子树
        PostOrder(T->rchild);  // 遍历右子树
        visit(T);              // 访问根结点
    }
}

4. 层序遍历(LevelOrder)—— 从上到下、从左到右

void LevelOrder(BiTree T) {
    Queue Q;
    InitQueue(&Q);
    EnQueue(&Q, T);
    while (!QueueEmpty(Q)) {
        BiTNode *p;
        DeQueue(&Q, &p);
        visit(p);
        if (p->lchild) EnQueue(&Q, p->lchild);
        if (p->rchild) EnQueue(&Q, p->rchild);
    }
}
记忆技巧「先 / 中 / 后」说的是根被访问的时机,左子树永远在右子树之前。所以三种 DFS 序列中,左子树的所有结点必定排在右子树之前

二、遍历序列示例

           A
         /   \
        B     C
       / \     \
      D   E     F
         /
        G
遍历方式序列
先序(根左右)A B D E G C F
中序(左根右)D B G E A C F
后序(左右根)D G E B F C A
层序(队列)A B C D E F G
交叉验证先序第一个 = 后序最后一个 = 根 A;中序里 A 左边是左子树 D B G E,右边是右子树 C F。做题时用这两条快速自查。

三、非递归遍历

1. 非递归中序遍历

void InOrderNonRec(BiTree T) {
    SqStack S;
    InitStack(&S);
    BiTNode *p = T;
    while (p || !StackEmpty(S)) {
        if (p) {
            Push(&S, p);       // 一路向左,边走边入栈
            p = p->lchild;
        } else {
            Pop(&S, &p);       // 出栈
            visit(p);          // 访问
            p = p->rchild;     // 转向右子树
        }
    }
}

2. 非递归先序遍历

void PreOrderNonRec(BiTree T) {
    SqStack S;
    InitStack(&S);
    BiTNode *p = T;
    while (p || !StackEmpty(S)) {
        if (p) {
            visit(p);          // 与中序唯一的区别:入栈前先访问
            Push(&S, p);
            p = p->lchild;
        } else {
            Pop(&S, &p);
            p = p->rchild;
        }
    }
}

3. 非递归后序遍历(最复杂)

需要用 r 记录上一个访问过的结点,以区分「右子树还没走」和「右子树刚走完」:

void PostOrderNonRec(BiTree T) {
    SqStack S;
    InitStack(&S);
    BiTNode *p = T, *r = NULL;  // r 记录上次访问的结点
    while (p || !StackEmpty(S)) {
        if (p) {
            Push(&S, p);
            p = p->lchild;
        } else {
            GetTop(S, &p);                       // 只看栈顶,不弹出
            if (p->rchild && p->rchild != r)     // 右子树未访问
                p = p->rchild;
            else {
                Pop(&S, &p);
                visit(p);
                r = p;                            // 记录刚访问的结点
                p = NULL;                         // 防止重复向左
            }
        }
    }
}
三处必须记牢后序非递归的三个关键:① 用 GetTop 而非 Pop 探查;② 用 r 判断右子树是否已访问;③ 访问后必须把 pNULL,否则会重新向左下沉造成死循环。

四、由遍历序列构造二叉树

核心定理

已知序列组合能否唯一确定原因
先序 + 中序先序给根,中序给左右分界
后序 + 中序后序末位给根,中序给分界
层序 + 中序层序首位给(子树)根
先序 + 后序无中序,无法区分「只有左孩子」与「只有右孩子」
一句话必须有中序:只有中序能告诉你「哪些结点在根的左边、哪些在右边」。

方法:先序 + 中序构造

  1. 先序第一个是根;
  2. 在中序中找到根,其左边是左子树、右边是右子树;
  3. 按左右子树的结点个数切分先序序列,递归构造。

方法:后序 + 中序构造

  1. 后序最后一个是根;
  2. 在中序中找到根,左边是左子树、右边是右子树;
  3. 递归构造左右子树。

手算示例

例 1:由先序 + 中序构造

先序:A B D E C F 中序:D B E A F C

  1. 先序第一个 A 是根;
  2. 中序中 A 左边 D B E 是左子树(3 个结点),右边 F C 是右子树(2 个结点);
  3. 左子树先序 B D E、中序 D B E → B 是根,D 左、E 右;
  4. 右子树先序 C F、中序 F C → C 是根,F 是孩子。
         A
       /   \
      B     C
     / \   /
    D   E F

例 2:由后序 + 中序构造

后序:D E B F C A 中序:D B E A F C

  1. 后序最后一个 A 是根;
  2. 中序中 A 左边 D B E、右边 F C
  3. 左子树后序 D E B → B 是根(后序末位),D 左、E 右;
  4. 右子树后序 F C → C 是根,F 是左孩子。

得到与例 1 完全相同的树 ✓

例 3:非递归中序遍历栈模拟

         A
       /   \
      B     C
     / \
    D   E
动作输出
1p=A,入栈,p=B[A]
2p=B,入栈,p=D[A,B]
3p=D,入栈,p=NULL[A,B,D]
4出栈 D,访问,p=D.rchild=NULL[A,B]D
5出栈 B,访问,p=B.rchild=E[A]D B
6p=E,入栈,p=NULL[A,E]D B
7出栈 E,访问,p=NULL[A]D B E
8出栈 A,访问,p=A.rchild=C[]D B E A
9p=C,入栈,p=NULL[C]D B E A
10出栈 C,访问,p=NULL[]D B E A C
11栈空且 p=NULL → 结束[]D B E A C

常见考法

考法 1先序 + 中序求后序序列(先建树再后序遍历,或熟练者直接递归切分)。
考法 2后序 + 中序求先序序列。
考法 3写出非递归中序 / 后序遍历算法(代码题高频)。
考法 4判断唯一性:先序 ABC、后序 CBA 能确定二叉树吗?不能 —— 先序 + 后序不能唯一确定。

易错点

必记
  1. 先序 + 后序不能唯一确定:至少需要中序(必背考点)。
  2. 构造时注意子树长度:中序切出多少个结点,先序 / 后序就切多少个。
  3. 非递归后序最复杂:需要 r 记录上一个访问的结点。
  4. 层序遍历用队列,不是栈。
  5. 先序第一个是根,后序最后一个是根,中序的根在中间(位置由子树规模决定)。

核心结论

组合能否唯一确定
先序 + 中序
后序 + 中序
层序 + 中序
先序 + 后序

时间复杂度:四种遍历均为 $O(n)$。
空间复杂度:递归 / 栈为 $O(h)$($h$ 为树高,最坏 $O(n)$);层序队列最坏 $O(n)$。

记忆卡片

先序、中序、后序的访问顺序?
先序 = 根左右;中序 = 左根右;后序 = 左右根。
层序遍历用什么数据结构?
队列(BFS)。
先序 + 后序能唯一确定二叉树吗?
不能,至少需要中序。
非递归中序遍历的核心思路?
一路向左入栈;出栈时访问;然后转向右子树。
先序序列第一个元素是什么?
根结点(后序最后一个也是根)。
遍历的时间 / 空间复杂度?
时间 $O(n)$;空间 $O(h)$,最坏(单支树)$O(n)$。

交互动画 · 四种遍历对照

A B C D E F G
(尚未开始)
当前模式:先序(根 → 左 → 右) —— 点「▶ 播放」或「下一步」开始
对比着看切换四个模式反复播放同一棵树:D 总是第一批被访问(在三种 DFS 中),而 A 在先序最先、后序最后、中序居中。这三条规律能帮你在考场上秒判序列合法性。

相关知识点

binary-tree-storage binary-tree-properties threaded-binary-tree

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