当前位置:   article > 正文

【LeetCode】二叉树的遍历(Java)--递归、迭代、Morris_二叉树遍历的递归算法的时间复杂度怎么算

二叉树遍历的递归算法的时间复杂度怎么算

一、二叉树的前序遍历

方法1:递归方法

思路与算法

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

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

  1. class Solution {
  2. public List<Integer> preorderTraversal(TreeNode root) {
  3. //递归方法
  4. List<Integer> res = new ArrayList<Integer>();
  5. preorder(root,res);
  6. return res;
  7. }
  8. public void preorder(TreeNode root,List<Integer> res){
  9. if(root == null){
  10. return;
  11. }else{
  12. res.add(root.val);
  13. preorder(root.left,res);
  14. preorder(root.right,res);
  15. }
  16. }
  17. }

复杂度分析

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

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

方法:2:迭代

思路与算法

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

  1. class Solution {
  2. public List<Integer> preorderTraversal(TreeNode root) {
  3. //迭代方法
  4. List<Integer> res = new ArrayList<Integer>();
  5. if(root == null){
  6. return res;
  7. }
  8. Deque<TreeNode> stack = new LinkedList<TreeNode>();
  9. while(!stack.isEmpty() || root != null){
  10. while(root != null){
  11. res.add(root.val);
  12. stack.push(root);
  13. root = root.left;
  14. }
  15. root = stack.pop();
  16. root = root.right;
  17. }
  18. return res;
  19. }
  20. }

复杂度分析

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

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

二、二叉树的中序遍历

方法1:递归

思路与算法

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

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

  1. class Solution {
  2. public List<Integer> inorderTraversal(TreeNode root) {
  3. //递归方法
  4. List<Integer> res = new ArrayList<Integer>();
  5. inorder(root,res);
  6. return res;
  7. }
  8. public void inorder(TreeNode root,List<Integer> res){
  9. if(root == null){
  10. return;
  11. }else{
  12. inorder(root.left,res);
  13. res.add(root.val);
  14. inorder(root.right,res);
  15. }
  16. }
  17. }

复杂度分析

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

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

方法2:迭代

  1. class Solution {
  2. public List<Integer> inorderTraversal(TreeNode root) {
  3. //迭代方法
  4. List<Integer> res = new ArrayList<Integer>();
  5. if(root == null){
  6. return res;
  7. }
  8. Deque<TreeNode> stack = new LinkedList<TreeNode>();
  9. while(!stack.isEmpty() || root != null){
  10. while(root != null){
  11. stack.push(root);
  12. root = root.left;
  13. }
  14. root = stack.pop();
  15. res.add(root.val);
  16. root = root.right;
  17. }
  18. return res;
  19. }
  20. }

复杂度分析

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

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

三、二叉树的后序遍历

方法1:递归

思路与算法

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

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

  1. class Solution {
  2. public List<Integer> postorderTraversal(TreeNode root) {
  3. //递归方法
  4. List<Integer> res = new ArrayList<Integer>();
  5. postorder(root,res);
  6. return res;
  7. }
  8. public void postorder(TreeNode root,List<Integer> res){
  9. if(root == null){
  10. return;
  11. }else{
  12. postorder(root.left,res);
  13. postorder(root.right,res);
  14. res.add(root.val);
  15. }
  16. }
  17. }

复杂度分析

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

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

方法2:迭代

  1. class Solution {
  2. public List<Integer> postorderTraversal(TreeNode root) {
  3. //迭代方法
  4. List<Integer> res = new ArrayList<Integer>();
  5. if(root == null){
  6. return res;
  7. }
  8. Deque<TreeNode> stack = new LinkedList<TreeNode>();
  9. TreeNode prev = null;//由于在某棵子树访问完之后,接着就要回溯到其父节点,因此使用prev来记录访问历史,在回溯到父节 点时,可以由此来判断,上一个访问的节点是否为右子树
  10. while(!stack.isEmpty() || root!=null){
  11. while(root!=null){
  12. stack.push(root);
  13. root = root.left;
  14. }
  15. root = stack.pop();//从栈中弹出的元素,左子树一定访问完了
  16. //现在需要确定的是是否有右子树,或者右子树是否访问过
  17. //如果没有右子树,或者右子树访问完了,也就是上一个访问的节点是右子节点时,说明可以访问当前根节点
  18. if(root.right == null || prev==root.right){
  19. res.add(root.val);
  20. prev = root; //更新历史访问记录,这样回溯的时候父节点可以由此判断右子树是否访问完成
  21. root = null;
  22. }else{
  23. //如果右子树没有被访问,那么将当前节点压栈,访问右子树
  24. stack.push(root);
  25. root = root.right;
  26. }
  27. }
  28. return res;
  29. }
  30. }

复杂度分析

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

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

四、二叉树的层序遍历

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

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

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

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

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

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

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

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

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

  1. class Solution {
  2. public List<List<Integer>> levelOrder(TreeNode root) {
  3. List<List<Integer>> res = new ArrayList<>();
  4. Queue<TreeNode> queue = new ArrayDeque<>();
  5. if(root!= null){
  6. queue.add(root);
  7. }
  8. while(! queue.isEmpty()){
  9. int n = queue.size();
  10. List<Integer> level = new ArrayList<>();
  11. for(int i = 0;i < n;i++){
  12. TreeNode node = queue.poll();
  13. level.add(node.val);
  14. if(node.left!= null){
  15. queue.add(node.left);
  16. }
  17. if(node.right!= null){
  18. queue.add(node.right);
  19. }
  20. }
  21. res.add(level);
  22. }
  23. return res;
  24. }
  25. }

复杂度分析

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

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/626802
推荐阅读
相关标签
  

闽ICP备14008679号