树与二叉树:从概念到遍历的全面解析

85 阅读4分钟

树与二叉树:从概念到遍历的全面解析

在计算机科学中,树(Tree) 是一种极其重要的非线性数据结构,广泛应用于文件系统、数据库索引、编译器语法分析、人工智能决策树等领域。本文将系统地介绍树的基本概念、二叉树的定义与特性,并深入讲解四种核心遍历方式——前序、中序、后序和层序遍历,辅以 JavaScript 代码实现,帮助读者建立清晰而完整的知识体系。


一、什么是树?

自然界中的树有根、枝、叶;数据结构中的正是对这一结构的抽象:

  • 根节点(Root) :整棵树的起点,有且仅有一个。
  • 边(Edge) :连接两个节点的线段,表示父子关系。
  • 节点(Node) :树的基本单元,包含数据和指向子节点的引用。
  • 叶子节点(Leaf) :没有子节点的节点,即“末端”。

树的核心属性

  1. 层次(Level)
    根节点位于第 1 层,其子节点为第 2 层,依此类推。

  2. 高度(Height)
    通常指从根节点到最远叶子节点的最长路径上的边数(也有定义为节点数)。例如,单个节点的树高度为 0 或 1,取决于定义方式。

  3. 度(Degree)
    一个节点的子节点数量称为该节点的度。整棵树的度是所有节点中最大的度。

    • 叶子节点的度为 0。
    • 度为 2 的节点有两个子节点。

⚠️ 注意:树 ≠ 所有节点度都为 2!这是常见误区。


二、二叉树:递归定义的典范

二叉树(Binary Tree) 是树的一种特殊形式,具有严格的结构约束:

定义(递归)

  • 空树是一棵二叉树;
  • 非空二叉树由一个根节点、一个左子树和一个右子树组成,且左右子树本身也必须是二叉树。

关键特性

  • 左右子树有严格顺序:左 ≠ 右,交换会改变树的结构。
  • 每个节点最多有两个子节点(左、右),但可以只有左、只有右,或都没有。
  • 不是所有度为 2 的树都是二叉树——二叉树强调“左右”之分,而普通树只关心子节点数量。

节点结构(JavaScript 表示)

class TreeNode {
    constructor(val) {
        this.val = val;
        this.left = null;
        this.right = null;
    }
}

也可以用对象字面量简洁表达:

const tree = {
    val: 1,
    left: { val: 2, left: { val: 4 }, right: { val: 5 } },
    right: { val: 3 }
};

这种方式直观、可读性强,常用于前端或算法题中的树表示。


三、二叉树的遍历:访问的艺术

遍历(Traversal)是指按某种顺序访问树中所有节点。由于树是非线性的,遍历方式决定了处理逻辑的顺序。

1. 深度优先遍历(DFS)——基于递归

深度优先遍历利用函数调用栈隐式实现,天然契合树的递归结构。

(1)前序遍历(Preorder):根 → 左 → 右
  • 应用场景:复制树、打印目录结构(先打印当前目录名)
function preorder(root) {
    if (!root) return;
    console.log(root.val);     // 访问根
    preorder(root.left);       // 遍历左子树
    preorder(root.right);      // 遍历右子树
}
(2)中序遍历(Inorder):左 → 根 → 右
  • 应用场景:二叉搜索树(BST)的中序遍历结果是升序序列
function inorder(root) {
    if (!root) return;
    inorder(root.left);
    console.log(root.val);     // 访问根
    inorder(root.right);
}
(3)后序遍历(Postorder):左 → 右 → 根
  • 应用场景:删除树(先删子节点再删父节点)、计算目录大小
function postorder(root) {
    if (!root) return;
    postorder(root.left);
    postorder(root.right);
    console.log(root.val);     // 访问根
}

记忆口诀
“前中后”指的是根节点被访问的时机,左右子树顺序始终不变。


2. 广度优先遍历(BFS)——层序遍历(Level Order)

层序遍历按树的层级从上到下、每层从左到右访问节点,需借助队列(Queue) 实现。

实现原理
  • 初始将根节点入队;
  • 循环出队一个节点,访问它,并将其非空子节点(先左后右)入队;
  • 直到队列为空。
function levelorder(root) {
    if (!root) return [];
    const result = [];
    const queue = [root];
    
    while (queue.length) {
        const node = queue.shift();      // 出队(FIFO)
        result.push(node.val);           // 访问
        if (node.left) queue.push(node.left);
        if (node.right) queue.push(node.right);
    }
    return result;
}
  • 时间复杂度:O(n),每个节点访问一次。
  • 空间复杂度:O(w),w 为树的最大宽度(队列最大长度)。

四、递归与树:天生一对

树的定义本身就是递归的,因此:

  • 递归是处理树问题的首选方法

  • 所有深度优先遍历本质上都是递归;

  • 递归的关键在于:

    1. 明确子问题(左子树、右子树);
    2. 设置终止条件if (!root) return)。

五、总结

特性说明
非线性结构,一个根,多分支,有层次
二叉树每个节点最多两个子节点,左右顺序不可交换
前序根→左→右,适合“先处理自身”场景
中序左→根→右,BST 中得有序序列
后序左→右→根,适合“先处理子节点”场景
层序按层遍历,需队列,体现广度优先

掌握树与二叉树的核心概念及遍历方法,是理解更高级数据结构(如堆、AVL 树、红黑树、B 树)和算法(如 DFS/BFS、动态规划树形 DP)的基础。无论是面试还是实际开发,这些知识都不可或缺。

🌳 记住:树的本质是递归,遍历的灵魂是顺序。