【算法相关】 - 树的迭代遍历

672 阅读2分钟

这是我参与8月更文挑战的第28天,活动详情查看:8月更文挑战

94. 二叉树的中序遍历

144. 二叉树的前序遍历

145. 二叉树的后序遍历

今天在复习树相关知识的时候想到了如上的问题,如果用递归来解决的话实际上是很简单的。

但是:如果使用迭代来替换递归,一时半会就想不出来了(查了一下,其实如果换成迭代,这个题目的难度就从easy到medium甚至hard了,因此一时间想不出来别灰心),因此现在总结一下树的递归转换到迭代的过程。

我们对递归下一个边界条件:递归:调用自身直到遇到边界条件。

在递归的时候,其实隐含的流程是:

  • 将当前的数据,压入方法栈
  • 通过传入的数据,进行下一步递归

因此,我们在递归转换到迭代的过程中,最重要的其实是确定当前位置

拿上面的遍历举例:

  • 前序遍历是最简单的,所以我们从这里开始。
//递归    
public static void preOrderTraversalR(List<Integer> list,TreeNode cur){
        list.add(cur.val);
        inorderTraversalR(list,cur.left);
        inorderTraversalR(list,cur.right);
    }
​
//迭代
    public static List<Integer> preOrderTraversal(TreeNode root) {
              Deque<TreeNode> stk = new ArrayDeque<>();
        List<Integer> res = new ArrayList<>();
        //仔细对比一下这里和循环的对应关系
        while(root!=null || !stk.isEmpty()){
            if(null == root) root = stk.pop();
            res.add(root.val);
            if(null!=root.right)stk.push(root.right);
            root = root.left;
        }
        return res;
    }

经过比较可以发现:

  • 迭代的时候,和递归相比我们的操作有一些固定的关系:

    • 先进行递归的,在迭代中是下一次执行对象
    • 我们本次迭代的整体流程是直到找到下一个加入结果集的对象结束

那么,我们根据这个发现,试着将中序遍历写出来:

    public static void inorderTraversalR(List<Integer> list,TreeNode cur){
        inorderTraversalR(list,cur.left);
        list.add(cur.val);
        inorderTraversalR(list,cur.right);
    }
​
​
public static List<Integer> inorderTraversal(TreeNode root) {
        Deque<TreeNode> stk = new LinkedList<>();
        List<Integer> res = new ArrayList<>();
        while(root!=null || !stk.isEmpty()){
            //这里对应:inorderTraversalR(list,cur.left);
            while(root!=null){
                stk.push(root);
                root = root.left;
            }
            //这里对应:list.add(cur.val);
            root = stk.pop();
            res.add(root.val);
            //这里对应:inorderTraversalR(list,cur.right);
            root = root.right;
        }
        return res;
    }

中序遍历的流程是:

  • 往左到最左
  • 入结果
  • 往右执行相同流程

其实这里有一个小问题,在下面的后序遍历会遇到,就是:

  • 我们是否需要一个节点,来记录上一个位置?

其实是不需要的,因为我们是左-中-右的顺序,这意味着我们经过的中节点是不需要再遍历的了。

同理可得后序遍历的:

    public static void postOrderTraversalR(List<Integer> list,TreeNode cur){
        inorderTraversalR(list,cur.left);
        inorderTraversalR(list,cur.right);
        list.add(cur.val);
    }
​
​
public static List<Integer> postOrderTraversal(TreeNode root) {
    List<Integer> res = new ArrayList<>();
    if (root == null) return res;
    Deque<TreeNode> stack = new LinkedList<>();
    TreeNode prev = null;
    while(null!=root || !stack.isEmpty()){
        //inorderTraversalR(list,cur.left);
        while(null!=root){
            stack.push(root);
            root = root.left;
        }
        //list.add(cur.val);
        root = stack.pop();
        //
        if(root.right==null || root.right==prev){
            res.add(root.val);
            //这里需要记录一下,因为我们没有像栈一样的本地记录可以恢复,因此我们要记录这个值,来确保不会进入死循环
            prev = root;
            root = null;
        }else{
            //inorderTraversalR(list,cur.right);
            stack.push(root);
            root = root.right;
        }
    }
    return res;
}
​
​

后序遍历会相对麻烦一些,因为我们遍历的顺序是左右中,这就意味着我们需要记录一下我们访问的右节点,否则会陷入死循环。

这里不能理解的话也没关系,可以先记下来再慢慢消化。

\