数据结构与算法-基础(八)遍历二叉树

1,099 阅读4分钟

「这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战

摘要

遍历二叉树是二叉树的基础操作,后面更多逻辑的其他二叉树要经常应用到遍历方式。其次,可以通过遍历二叉树来理解递归思想和对队列的应用。

遍历是数据结构中的常见操作,就是把所有的元素遍历一遍。线性结构的遍历无非是两种,正序遍历逆序遍历,也就是从头依次遍历或者从尾依次遍历。

遍历二叉树就是从根节点开始向叶子节点遍历,每个节点除自身还有它的左子树或者右子树,所以根据不同顺序遍历这三个节点的情况,定义出不同的遍历方式。

遍历方式有 4 种,是根据不同的节点访问顺序来区分

遍历方法访问顺序
前序遍历(Preorder Traversal)根节点、左子树、右子树
中序遍历(Inorder Traversal)左子树、根节点、右子树
后序遍历(Postorder Traversal)左子树、右子树、根节点
层序遍历(Level Order Traversal)从上到下、从左到右依次访问每一个节点

从上面表格中可以大致总结出前三种遍历的区别就是访问这三种节点的顺序不同。不管是左子树,还是右子树,都可以当作是一个整体(比如一个节点),自身第一个节点也可以当作子树范围的根节点,那么遍历遍历子树,就是不断的向下把子树当作一个新的二叉树来处理,直到遍历到叶子节点。

按照上面的逻辑梳理,可以使用递归的方式来前序遍历中序遍历后序遍历

注意:凡是使用递归方式,必须要确定一个结束递归的条件,因为二叉树的遍历都是从根节点开始,直到所有节点都遍历完成,所以结束递归的条件就是节点为 null

// 结束递归条件
if (node == null) return;

前序遍历的顺序是根节点、左子树,最后右子树

public void preorderTanversal() {
  preorderTanversal(root, visitor);
}
private void preorderTanversal(Node<E> node) {
  // 结束条件
  if (node == null) return;
​
  // 访问根节点
  System.out.println(node.element);
  // 访问左子树
  preorderTanversal(node.left, visitor);
  // 访问右子树
  preorderTanversal(node.right, visitor);
}

中序遍历的顺序是左子树、根节点,最后右子树

/**
 * 中序遍历(递归)
 */
public void inorderTraversal() {
  preorderTanversal(root, visitor);
}
private void inorderTraversal(Node<E> node) {
  // 结束条件
  if (node == null) return;
​
  // 访问左子树
  inorderTraversal(node.left, visitor);
  // 访问根节点
  System.out.println(node.element);
  // 访问右子树
  inorderTraversal(node.right, visitor);
}

特别:中序遍历是只要保证根节点是在中间访问,那么先访问左子树还是先访问右子树,根据心情来决定就好。

后序遍历的顺序是左子树、右子树,最后是根节点:

/**
 * 后序遍历(递归)
 */
public void postorderTraversal() {
  preorderTanversal(root, visitor);
}
private void postorderTraversal(Node<E> node) {
  // 结束条件
  if (node == null) return;
​
  // 访问左子树
  postorderTraversal(node.left, visitor);
  // 访问右子树
  postorderTraversal(node.right, visitor);
  // 访问根节点
  System.out.println(node.element);
}

层序遍历的访问顺序是从上到下,从左到右依次遍历。也就是把二叉树从根节点到叶子节点结构,把每一层的节点从左到右遍历完成。这种遍历方式有一个必须要注意的地方,就是一层没有遍历完,绝不能遍历下一层的节点

恰好,队列的结构的 FIFO 模式,能够保证序列中的元素绝对顺序不被破坏,而且只有一个入口和出口。所以就可以把二叉树的节点从根节点开始到叶子节点一一入队,并在另一端出队。出队的同时,也可以将自己的左右子节点入队,不影响当前序列中的节点顺序。可以是非常完美。

这里只需要留意让节点左右子树依次入队的处理,即存在就入队,不存在就出队,知道队列中的元素数量是 null,就是遍历完成了。

public void leverOrderTraversal() {
  if (root == null) return;
  // 创建队列
  Queue<Node<E>> queue = new LinkedList<>();
​
  // 第一节点入队
  queue.offer(root);
  while (!queue.isEmpty()) {
    
    // 处理事件
    Node<E> node = queue.poll();
    System.out.println(node.element);
​
    // 依次将存在的左右子树入队
    if (node.left != null) {
      queue.offer(node.left);
    }
    if (node.right != null) {
      queue.offer(node.right);
    }
  }
}

层序遍历是一个非常有用的遍历方式,在后面的计算二叉树的高度,或者去判断是否是完全二叉树方面,都有应用到,所以理解用队列来处理是非常重要的。