非递归方式实现二叉树的先序、中序、后序遍历

207 阅读4分钟

前言

在之前的文章里介绍过如何使用递归的方式来实现二叉树的先序、中序、后序遍历,这篇文章来介绍下如何采用非递归的方式实现二叉树的先序、中序、后序遍历。

正文

其实任何递归函数都可以用非递归的方式来实现,递归函数在系统上实现时涉及到函数的入栈和出栈,既然要用非递归的方式来实现,这就需要我们自己来实现入栈和出栈。

如图所示,图中二叉树:

  • 先序遍历为:1->2->4->5->3->6->7
  • 中序遍历为:4->2->5->1->6->3->7
  • 后序遍历为:4->5->2->6->7->3->1

下面来一起分析下使用非递归的二叉树先序、中序、后序遍历。

先序遍历

先序遍历的顺序是:访问根节点(头)->遍历左子树(左)->遍历右子树(右),简称:头->左->右。

使用非递归时,先准备一个栈:

  1. 先把头结点压栈,毫无道理可言,节点1入栈。
  2. 将栈顶元素节点1弹出,记为current节点1出栈。
  3. 弹出的节点如果有右节点则将右节点入栈,如果有左节点后将左节点入栈,先右后左,节点3先入栈,节点2后入栈。
  4. 节点2弹出,将节点5入栈,将节点4入栈。
  5. 节点4弹出,节点4没有子节点,直接弹出,不入栈。
  6. 节点5弹出,节点5没有子节点,直接弹出,不入栈。
  7. 节点3弹出,将节点7入栈,将节点6入栈。
  8. 节点6弹出,节点6没有子节点,直接弹出,不入栈。
  9. 节点7弹出,节点7没有子节点,直接弹出,不入栈。
  10. 栈中没有节点了,结束。

从上面节点弹出的顺序即为二叉树的先序遍历,可以将过程简化如下:

  1. 先将根节点入栈
  2. 弹出栈顶元素,并用一个变量记录。
  3. 先将弹出元素的右节点入栈,后将左节点入栈。

循环步骤2、3,直到栈中没有元素。

代码实现

public static void pre(Node head) {
   if (head != null) {
      Stack<Node> stack = new Stack<>();
      stack.push(head);
      while (!stack.isEmpty()) {
         head = stack.pop();
         System.out.print(head.value + " ");
         if (head.right != null) {
            stack.push(head.right);
         }
         if (head.left != null) {
            stack.push(head.left);
         }
      }
   }
   System.out.println();
}

中序遍历

中序遍历的顺序是:遍历左子树(左)->访问根节点(头)->遍历右子树(右),简称:左->头->右。

用栈来实现中序遍历有点麻烦,流程如下:

  1. 先把一个数的所有左边界从上到下进行压栈,1->2->4
  2. 弹出节点4并打印节点4的值,判断节点4的右节点是否为空,为空的话,继续弹出。
  3. 弹出节点2并打印节点2的值,将节点2的右节点——节点5以及其所在子树的所有左边界入栈。
  4. 弹出节点5并打印节点5的值,节点5无右节点,继续弹出。
  5. 弹出节点1并打印节点1的值,并将节点3节点6入栈。
  6. 弹出节点6并打印节点6的值,节点6无右节点,继续弹出。
  7. 弹出节点3并打印节点3的值,并将节点7入栈。
  8. 弹出节点7并打印节点7的值,节点7无右节点,继续弹出。
  9. 栈中所有几点全部弹出,此时中序遍历完成。

从上面节点弹出的顺序即为二叉树的中序遍历,可以将过程简化如下:

  1. 先将根节点记为currentcurrent节点的所在左边界节点入栈。
  2. 弹出栈顶元素,并打印节点值。
  3. 将弹出节点的右节点作为current,重复步骤1~3。

代码实现

public static void in(Node current) {
  if (current != null) {
    Stack<Node> stack = new Stack<>();
    while (!stack.isEmpty() || current != null) {
      if (current != null) {
        stack.push(current);
        current = current.left;
      } else {
        current = stack.pop();
        System.out.print(current.value + " ");
        current = current.right;
      }
    }
  }
  System.out.println();
}

后序遍历

后序遍历的顺序是:遍历左子树(左)->遍历右树(右)->访问根节点(头),简称:左->右->头。

后序遍历和先序遍历有点相似,先序遍历的顺序为“头->左->右”,更改下节点放入顺序就是"头->右->左",再把"头->右->左"逆序就得到了“左->右->头”。

来看下具体过程,根据先序遍历的步骤,入栈时先将右节点入栈后将左节点入栈,我们稍微改造下,先将左节点入栈,后将右节点入栈。

在弹出节点时,先不将节点的值打印出来,而是准备一个新栈,将弹出的节点放入新栈中,等旧栈所有元素都弹出完,再将新栈的元素弹出并打印,这时打印的顺序即为后序遍历。

代码实现

public static void pos(Node head) {
   if (head != null) {
      Stack<Node> stack1 = new Stack<>();
      Stack<Node> stack2 = new Stack<>();
      stack1.push(head);
      while (!stack1.isEmpty()) {
         head = stack1.pop();
         stack2.push(head);
         if (head.left != null) {
            stack1.push(head.left);
         }
         if (head.right != null) {
            stack1.push(head.right);
         }
      }
      while (!stack2.isEmpty()) {
         System.out.print(stack2.pop().value + " ");
      }
   }
   System.out.println();
}

总结

本篇文章主要介绍如何采用非递归的方式实现二叉树的先序、中序、后序遍历,其中在实现后序遍历的时候,采用了两个栈,其实也可以用一个栈实现的,不过相对复杂点,这里就先不介绍了~