解锁二叉树:前序、中序、后序遍历

树形结构是一种重要的数据结构,它由节点和连接节点的边组成。树形结构的遍历是指按照一定顺序访问数的所有节点。在二叉树中,常见的遍历方式有前序遍历中序遍历后序遍历。这些遍历方式在不同场景下有着广泛的应用,特别是在处理递归问题和数据结构的操作时。

1. 前序遍历(Pre-order Traversal)

定义:在前序遍历中,先访问根节点,然后递归地遍历左子树,最后递归地遍历右子树。

前序遍历的访问顺序是:根节点 -> 左子树 -> 右子树。

1.1 代码实现

1、递归方式

递归的前序遍历非常直接,每访问一个节点就立即处理该节点,然后递归访问左子树和右子树。这种实现容易理解,代码简洁。

// 二叉树节点结构
class TreeNode {
  constructor(value) {
    this.value = value;
    this.left = null;
    this.right = null;
  }
}

// 前序遍历(递归)
function preOrderTraversal(root) {
  if (root === null) return; // 空树直接返回
  console.log(root.value); // 访问根节点
  preOrderTraversal(root.left); // 遍历左子树
  preOrderTraversal(root.right); // 遍历右子树
}

// 示例二叉树
//        1
//       / \
//      2   3
//     / \
//    4   5

let root = new TreeNode(1);
root.left = new TreeNode(2);
root.right = new TreeNode(3);
root.left.left = new TreeNode(4);
root.left.right = new TreeNode(5);

preOrderTraversal(root); // 输出: 1 2 4 5 3

2、非递归方式

通过 栈 模拟递归过程,栈用于保存当前节点,并控制遍历顺序。非递归实现的关键是先访问根节点,然后将右子节点和左子节点入栈(顺序需要反过来,保证左子树优先处理)。

function preOrderTraversalNonRecursive(root) {
  if (!root) return;
  let stack = [root];
  while (stack.length) {
    let node = stack.pop();
    console.log(node.value);  // 处理根节点
    if (node.right) stack.push(node.right);  // 先右后左
    if (node.left) stack.push(node.left);  // 确保左子树优先
  }
}
1.2 适用场景

1、文件系统遍历

操作系统中文件系统是树形结构,前序遍历可以用来遍历目录和文件。比如,列出所有文件时,可能会先访问当前目录,再访问子目录和文件。

2、序列化树

如果需要将树转换为字符串进行存储或传输,前序遍历通常是最常用的选择。因为可以按节点顺序来记录树的结构,确保树的结构能够被复原。

2. 中序遍历(In-order Traversal)

定义:中序遍历的顺序是:先递归地遍历左子树,接着访问根节点,然后递归地遍历右子树。

中序遍历的访问顺序是:左子树 -> 根节点 -> 右子树。

2.1 代码实现

1、递归方式

递归实现中序遍历时,首先递归遍历左子树,然后处理根节点,最后递归遍历右子树。实现简单:

// 中序遍历(递归)
function inOrderTraversal(root) {
  if (root === null) return; // 空树直接返回
  inOrderTraversal(root.left); // 遍历左子树
  console.log(root.value); // 访问根节点
  inOrderTraversal(root.right); // 遍历右子树
}

inOrderTraversal(root); // 输出: 4 2 5 1 3

2、非递归方式

使用栈来模拟递归过程。栈会保存当前遍历的路径,每当我们到达一个节点的左子节点,就压入栈。当节点的左子树遍历完毕后,处理当前节点,然后递归遍历右子树。

function inOrderTraversalNonRecursive(root) {
  let stack = [];
  let current = root;
  while (stack.length > 0 || current !== null) {
    while (current !== null) {
      stack.push(current);
      current = current.left;  // 一直遍历左子树
    }
    current = stack.pop();
    console.log(current.value);  // 处理根节点
    current = current.right;  // 遍历右子树
  }
}
2.2 适用场景

1、二叉搜索树的排序

在二叉搜索树(BST)中,中序遍历可以得到一个递增序列,因为在 BST 中,左子树的值小于根节点,右子树的值大于根节点,所以中序遍历自然地按照升序顺序访问节点。

2、表达式树的中缀表达式

中序遍历非常适用于计算表达式树的中缀表达式(如 a + b * c ),遍历过程中可以直接得到正确的符号顺序。

3. 后序遍历(Post-order Traversal)

定义:在后序遍历中,首先递归地遍历左子树,然后递归地遍历右子树,最后访问根节点。

后序遍历的访问顺序是:左子树 -> 右子树 -> 根节点。

3.1 代码实现

1、递归方式

递归实现时,先遍历左子树,然后遍历右子树,最后处理根节点。递归的顺序最直接地反映了“先销毁子节点再销毁父节点”的思想:

// 后序遍历(递归)
function postOrderTraversal(root) {
  if (root === null) return; // 空树直接返回
  postOrderTraversal(root.left); // 遍历左子树
  postOrderTraversal(root.right); // 遍历右子树
  console.log(root.value); // 访问根节点
}

postOrderTraversal(root); // 输出: 4 5 2 3 1

2、非递归方式

非递归实现较复杂,通常使用两个 栈 来辅助实现,或者使用一个栈并处理节点的状态。关键是要确保在遍历到一个节点时,其左右子树已被完全访问。

function postOrderTraversalNonRecursive(root) {
  if (!root) return;
  let stack = [root];
  let output = [];
  while (stack.length) {
    let node = stack.pop();
    output.push(node.value);
    if (node.left) stack.push(node.left);
    if (node.right) stack.push(node.right);
  }
  // 输出的节点是根节点在最后处理,所以要反转结果
  while (output.length) {
    console.log(output.pop());
  }
}
3.2 适用场景

1、树节点的销毁

后序遍历常用于树形结构的销毁操作,因为它保证了先删除子节点,再删除父节点。比如,在删除一个节点时,确保它的子节点已经被正确释放。

2、表达式树的求值

后序遍历非常适用于表达式树的后缀表达式求值。在求值时,先处理操作数,再执行操作符,符合后缀表达式的计算规则。

4. 前序、中序、后序遍历的关系与重建二叉树

1、前序 + 中序

给定前序和中序遍历结果,可以唯一地重建一棵二叉树。前序遍历告诉我们根节点,而中序遍历帮助我们确定根节点左右子树的边界。

2、中序 + 后序

给定中序和后序遍历结果,也可以重建二叉树。后序遍历提供了根节点的信息,而中序遍历则帮助我们拆分左右子树。

你可能感兴趣的:(数据结构,算法,java,数据结构,树的遍历)