为啥你们都要求不能递归遍历二叉树啊

69 阅读5分钟

就今天吧,我要把二叉树的遍历一次性全搞懂!

前言:

大家好,我是Felix, 在参加一个外企(**证券公司)的面试时候,上来面试官就给了我一张纸,写个二叉树遍历吧,我心想这还不简单,提笔就要开干!

面试管:不许用递归!

我:天塌了!

img

于是我决心回去一定要把二叉树搞懂,随便你咋考,我都能答。

1. 什么是二叉树

二叉树是一种常见的数据结构,由一个或者多个节点组成,每个节点都会有一个左节点或者右节点。

image.png

看到图形我们也就很容易理解了二叉树的概念了,每个节点除了记录本身的值以外,还有两个指针指向左节点和父节点,当然,这个指针也可以为NULL。PS: 原谅我的图画的比较难看哈

那这个时候,我们会有一个问题了,你们在工作中会有真实业务用到这个二叉树吗,我碰到的还是很少的,但是它是一种思想,学习还是很有意义的。

好的,那我们那手写一个二叉树吧。

public class TreeNode {
      int val;
    
      TreeNode left;
    
      TreeNode right;
    
      TreeNode(int val) { 
          this.val = val;
      }
    
      TreeNode(int val, TreeNode left, TreeNode right) {
          this.val = val;
          this.left = left;
          this.right = right;
      }

}

2. 如何去遍历一个二叉树

二叉树已经有了,那我们如何去遍历这个二叉树呢,感觉和我们平时学习的list, map, set是完全不一样的数据结构

不慌,我们先梳理一些概念,一颗二叉树,它的节点会有三种,根节点,左节点,右节点。

因此,我们可以根据根节点遍历的前中后不同,区分出中序,前序,后续遍历

例如: 我们想要中序遍历,那么就应该先左节点,再根节点,最后右节点。

如果是上图的二叉树的话,遍历顺序应该是这样的 2 3 4 5 1 6 7

那前序和后序遍历,也是以此类推了。

那用代码如何实现呢?

由于二叉树的特性,我们其实特别容易想到的就是递归,因为他的结构就有递归特性,我们从根节点出发寻找左节点,发现要找到最左边的2,需要一直往下,直到找到这个节点的左节点为NULL 为止。看下代码实现

public static void main(String[] args) {
        TreeNode node2 = new TreeNode(2);
        TreeNode node4 = new TreeNode(4);
        TreeNode node1 = new TreeNode(1);
        TreeNode node7 = new TreeNode(7);
        TreeNode node3 = new TreeNode(3, node2, node4);
        TreeNode node6 = new TreeNode(6, node1, node7);
        TreeNode node5 = new TreeNode(5, node3, node6);

        ArrayList<Integer> ans = new ArrayList<>();
        accessNode(node5, ans);

        for (int num : ans)
            System.out.print(num + " ");
    }



    public static void accessNode (TreeNode root, ArrayList<Integer> ans) {
        if (root == null) return;
        accessNode(root.left, ans);
        ans.add(root.val);
        accessNode(root.right, ans);
    }


可以看到打印结果也是符合我们预期的,代码也是比较简单的。

那这是,你就要问了,如果是后序或者前序遍历是不是只要调整递归的顺序就可以了呢?

完全没错!

这里我列出前序和后序的代码

//前序遍历,只需要先记录结果,再去访问左节点和右节点即可
public static void accessNode (TreeNode root, ArrayList<Integer> ans) {
        if (root == null) return;
        ans.add(root.val);
        accessNode(root.left, ans);
        accessNode(root.right, ans);
}

//后序遍历,只需先去访问左节点和右节点,最后记录结果即可
public static void accessNode (TreeNode root, ArrayList<Integer> ans) {
        if (root == null) return;
        ans.add(root.val);
        accessNode(root.left, ans);
        accessNode(root.right, ans);
}

好的,到了这里,我们会发现,小小二叉树不过如此嘛

img

但是,很多大厂都很讨厌,你不用递归可以实现吗?

这时候我们要想到一种数据结构,那就是栈,因为二叉树在遍历的过程中,很多时候最先访问到的节点我们只是经过,最后找到的才回去记录结果。

那栈这种数据结构就可以完成这一步,对于经过的节点,我们可以压栈,那需要记录结果的就会在最上面,弹栈记录这样子。

代码实现如下:

中序遍历

public static List<Integer> middleIterator(TreeNode root) {
    //创建一个list保存结果
    List<Integer> ans = new ArrayList<>();
    //创建一个栈,用来实现遍历
    Stack<TreeNode> stack = new Stack<>();
    while (null != root || !stack.empty()) {
        //这里我们先把所有的左节点全部压入栈中
        while (null != root) {
            stack.push(root);
            root = root.left;
        }

        //弹出来的节点就要记录结果,并处理他的右节点
        TreeNode node = stack.pop();
        ans.add(node.val);
        root = node.right;
    }

    return ans;
}

前序遍历

public static List<Integer> middleIterator(TreeNode root) {
        
        List<Integer> ans = new ArrayList<>();
        
        Stack<TreeNode> stack = new Stack<>();
        while (null != root || !stack.empty()) {
            
            while (null != root) {
                //和中序遍历相比,代码的变化就在这一行,根节点直接就记录结果了
                ans.add(root.val);
                stack.push(root);
                root = root.left;
            }

            
            TreeNode node = stack.pop();
            root = node.right;
        }

        return ans;
    }

后序遍历

public static List<Integer> afterIterator(TreeNode root) {
       
        List<Integer> ans = new ArrayList<>();
        
        Stack<TreeNode> stack = new Stack<>();
    	//后序遍历的过程中,由于我们不知道一个中间节点是否已经遍历过右节点了,因为要用一个节点去保存下上一个遍历的节点
        TreeNode preNode = null;
        while (null != root || !stack.empty()) {
           while (null != root) {
               stack.push(root);
               root = root.left;
           }

           TreeNode node = stack.pop();
           //该条件下表明,该节点右节点为空或者右节点已经被遍历过了,我们可以记录结果了,并且要记录该节点,且把root置空,继续弹栈遍历
           if (node.right == null || preNode == node.right) {
               ans.add(node.val);
               preNode = node;
               root = null;
           } else {
               //该条件下,表明该节点的右节点还没遍历过呢,因为,要把它重新压栈,右节点加入循环遍历
               stack.push(node);
               root = node.right;
           }
        }

        return ans;
    }

下一个章节,我们来说二叉树的层序遍历,谢谢观看!