【LeetCode】二叉树的遍历(Java)--递归、迭代、Morris

一、二叉树的前序遍历

方法1:递归方法

思路与算法

首先我们需要了解什么是二叉树的前序遍历:按照访问根节点——左子树——右子树的方式遍历这棵树,而在访问左子树或者右子树的时候,我们按照同样的方式遍历,直到遍历完整棵树。因此整个遍历过程天然具有递归的性质,我们可以直接用递归函数来模拟这一过程。

定义 preorder(root) 表示当前遍历到 root 节点的答案。按照定义,我们只要首先将 root 节点的值加入答案,然后递归调用 preorder(root.left) 来遍历 root 节点的左子树,最后递归调用 preorder(root.right) 来遍历 root 节点的右子树即可,递归终止的条件为碰到空节点。

class Solution {
    public List preorderTraversal(TreeNode root) {
        //递归方法
        List res = new ArrayList();
        preorder(root,res);
        return res;
    }
    public void preorder(TreeNode root,List res){
        if(root == null){
            return;
        }else{
            res.add(root.val);
            preorder(root.left,res);
            preorder(root.right,res);
        }
    }
}

复杂度分析

时间复杂度:O(n),其中 nn 是二叉树的节点数。每一个节点恰好被遍历一次。

空间复杂度:O(n),为递归过程中栈的开销,平均情况下为 O(logn),最坏情况下树呈现链状,为 O(n)。

方法:2:迭代

思路与算法

我们也可以用迭代的方式实现方法一的递归函数,两种方式是等价的,区别在于递归的时候隐式地维护了一个栈,而我们在迭代的时候需要显式地将这个栈模拟出来,其余的实现与细节都相同。

class Solution {
    public List preorderTraversal(TreeNode root) {

     //迭代方法
     List res = new ArrayList();
     if(root == null){
        return res;
    }
    Deque stack = new LinkedList();
    
    while(!stack.isEmpty() || root != null){
        while(root != null){
            res.add(root.val);
            stack.push(root);
            root = root.left;
        }
        root = stack.pop();
        root = root.right;
    }
    return res;
    }
}

复杂度分析

时间复杂度:O(n),其中 nn 是二叉树的节点数。每一个节点恰好被遍历一次。

空间复杂度:O(n),为迭代过程中显式栈的开销,平均情况下为 O(logn),最坏情况下树呈现链状,为 O(n)。

二、二叉树的中序遍历

方法1:递归

思路与算法

首先我们需要了解什么是二叉树的中序遍历:按照访问左子树——根节点——右子树的方式遍历这棵树,而在访问左子树或者右子树的时候我们按照同样的方式遍历,直到遍历完整棵树。因此整个遍历过程天然具有递归的性质,我们可以直接用递归函数来模拟这一过程。

定义 inorder(root) 表示当前遍历到root 节点的答案,那么按照定义,我们只要递归调用 inorder(root.left) 来遍历root 节点的左子树,然后将root 节点的值加入答案,再递归调用inorder(root.right) 来遍历 root 节点的右子树即可,递归终止的条件为碰到空节点。

class Solution {
    public List inorderTraversal(TreeNode root) {  
        //递归方法
        List res = new ArrayList();
        inorder(root,res);
        return res;
    }
    public void inorder(TreeNode root,List res){
        if(root == null){
            return;
        }else{
            inorder(root.left,res);
            res.add(root.val);
            inorder(root.right,res);
        }
    }
    
}

复杂度分析

时间复杂度:O(n),其中 nn 为二叉树节点的个数。二叉树的遍历中每个节点会被访问一次且只会被访问一次。

空间复杂度:O(n)。空间复杂度取决于递归的栈深度,而栈深度在二叉树为一条链的情况下会达到 O(n) 的级别。

方法2:迭代

class Solution {
    public List inorderTraversal(TreeNode root) {
 
        //迭代方法
        List res = new ArrayList();
        if(root == null){
            return res;
        }

        Deque stack = new LinkedList();
        while(!stack.isEmpty() || root != null){
            while(root != null){
                stack.push(root);
                root = root.left;
            }
            root = stack.pop();
            res.add(root.val);
            root = root.right;
        }
        return res;
    }
}

复杂度分析

时间复杂度:O(n),其中 nn 为二叉树节点的个数。二叉树的遍历中每个节点会被访问一次且只会被访问一次。

空间复杂度:O(n)。空间复杂度取决于栈深度,而栈深度在二叉树为一条链的情况下会达到 O(n) 的级别。

三、二叉树的后序遍历

方法1:递归

思路与算法

首先我们需要了解什么是二叉树的后序遍历:按照访问左子树——右子树——根节点的方式遍历这棵树,而在访问左子树或者右子树的时候,我们按照同样的方式遍历,直到遍历完整棵树。因此整个遍历过程天然具有递归的性质,我们可以直接用递归函数来模拟这一过程。

定义 postorder(root) 表示当前遍历到 root 节点的答案。按照定义,我们只要递归调用 postorder(root->left) 来遍历 root 节点的左子树,然后递归调用 postorder(root->right) 来遍历 root 节点的右子树,最后将 root 节点的值加入答案即可,递归终止的条件为碰到空节点。

class Solution {
    public List postorderTraversal(TreeNode root) {
        //递归方法
        List res = new ArrayList();
        postorder(root,res);
        return res;
    }
    public void postorder(TreeNode root,List res){
        if(root == null){
            return;
        }else{
            postorder(root.left,res);
            postorder(root.right,res);
            res.add(root.val);
        }
    }

}

复杂度分析

时间复杂度:O(n),其中 nn 是二叉搜索树的节点数。每一个节点恰好被遍历一次。

空间复杂度:O(n),为递归过程中栈的开销,平均情况下为 O(logn),最坏情况下树呈现链状,为 O(n)。

方法2:迭代

class Solution {
    public List postorderTraversal(TreeNode root) {

        //迭代方法
        List res = new ArrayList();
        if(root == null){
            return res;
        }
        Deque stack = new LinkedList();
        TreeNode prev = null;//由于在某棵子树访问完之后,接着就要回溯到其父节点,因此使用prev来记录访问历史,在回溯到父节   点时,可以由此来判断,上一个访问的节点是否为右子树

        while(!stack.isEmpty() || root!=null){
            while(root!=null){
                stack.push(root);
                root = root.left;
            }
            root = stack.pop();//从栈中弹出的元素,左子树一定访问完了

            //现在需要确定的是是否有右子树,或者右子树是否访问过
            //如果没有右子树,或者右子树访问完了,也就是上一个访问的节点是右子节点时,说明可以访问当前根节点
            if(root.right == null || prev==root.right){
                res.add(root.val);
                prev = root; //更新历史访问记录,这样回溯的时候父节点可以由此判断右子树是否访问完成
                root = null;
            }else{
                //如果右子树没有被访问,那么将当前节点压栈,访问右子树
                stack.push(root);
                root = root.right;
            }
        }
        return res;
    }
}

复杂度分析

时间复杂度:O(n),其中 nn 是二叉搜索树的节点数。每一个节点恰好被遍历一次。

空间复杂度:O(n),为迭代过程中显式栈的开销,平均情况下为O(logn),最坏情况下树呈现链状,为 O(n)。

四、二叉树的层序遍历

使用的是广度优先搜索(BFS)

乍一看来,二叉树层序遍历顺序和 BFS 是一样的,我们可以直接用 BFS 得出层序遍历结果。然而,层序遍历要求的输入结果和 BFS 是不同的。层序遍历要求我们区分每一层,也就是返回一个二维数组。而 BFS 的遍历结果是一个一维数组,无法区分每一层。

【LeetCode】二叉树的遍历(Java)--递归、迭代、Morris_第1张图片

那么,怎么给 BFS 遍历的结果分层呢?我们首先来观察一下 BFS 遍历的过程中,结点进队列和出队列的过程:

【LeetCode】二叉树的遍历(Java)--递归、迭代、Morris_第2张图片

截取 BFS 遍历过程中的某个时刻:

【LeetCode】二叉树的遍历(Java)--递归、迭代、Morris_第3张图片

可以看到,此时队列中的结点是 3、4、5,分别来自第 1 层和第 2 层。这个时候,第 1 层的结点还没出完,第 2 层的结点就进来了,而且两层的结点在队列中紧挨在一起,我们无法区分队列中的结点来自哪一层

因此,我们需要稍微修改一下代码,在每一层遍历开始前,先记录队列中的结点数量 n(也就是这一层的结点数量),然后一口气处理完这一层的 n个结点。

【LeetCode】二叉树的遍历(Java)--递归、迭代、Morris_第4张图片

这样,我们就将 BFS 遍历改造成了层序遍历。在遍历的过程中,结点进队列和出队列的过程为:

【LeetCode】二叉树的遍历(Java)--递归、迭代、Morris_第5张图片

可以看到,在 while 循环的每一轮中,都是将当前层的所有结点出队列,再将下一层的所有结点入队列,这样就实现了层序遍历。

最终我们得到的题解代码为:

class Solution {
    public List> levelOrder(TreeNode root) {
        List> res = new ArrayList<>();
        Queue queue = new ArrayDeque<>();

        if(root!= null){
            queue.add(root);
        }
        while(! queue.isEmpty()){
            int n = queue.size();
            List level = new ArrayList<>();
            for(int i = 0;i < n;i++){
                TreeNode node = queue.poll();
                level.add(node.val);
                if(node.left!= null){
                    queue.add(node.left);
                }
                if(node.right!= null){
                    queue.add(node.right);
                }
            }
            res.add(level);
        }
        return res;
    }
}

复杂度分析

记树上所有节点的个数为 n。

时间复杂度:每个点进队出队各一次,故渐进时间复杂度为 O(n)。
空间复杂度:队列中元素的个数不超过 n个,故渐进空间复杂度为O(n)。

你可能感兴趣的:(java,leetcode,b树,算法)