树的遍历(前序、中序、后序,递归、非递归,层次)

本文详细介绍了树的四种遍历方法——前序、中序、后序和层次遍历——的递归与迭代实现。对于每种遍历方式,都提供了详细的代码实现,包括使用栈进行迭代以及 Morris 遍历算法。这些方法适用于二叉树的数据结构,帮助理解树的遍历原理和操作技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一.递归

1.前序遍历

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<Integer>();
        preorder(root, result);
        return result;
    }

    public void preorder(TreeNode node, List<Integer> result) {
        if (node == null) {
            return;
        }
		//核心代码
        result.add(node.val);
        preorder(node.left, result);
        preorder(node.right, result);
    }
}

2.中序遍历

class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        inorder(root, result);
        return result;
    }

    public void inorder(TreeNode node, List<Integer> result) {
        if (node == null) {
            return;
        }
		//核心代码
        inorder(node.left, result);
        result.add(node.val);
        inorder(node.right, result);
    }
}

3.后续遍历

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        postorder(root, result);
        return result;
    }

    public void postorder(TreeNode node, List<Integer> result) {
        if (node == null) {
            return;
        }
		//核心代码
        postorder(node.left, result);
        postorder(node.right, result);
        result.add(node.val);
    }
}

二.迭代

1.前序遍历

//入栈顺序:中-右-左
class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        if (root == null) {
            return result;   
        }
        
        Deque<TreeNode> stack = new ArrayDeque<>();
        stack.push(root);
        TreeNode node = new TreeNode();
        while (!stack.isEmpty()) {
            node = stack.pop();
            result.add(node.val);
            
            if (node.right != null) {
                stack.push(node.right);
            }
            if (node.left != null) {
                stack.push(node.left);
            }
        }
        return result;
    }
}

2.中序遍历

class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        if (root == null) {
            return result;
        }

        Deque<TreeNode> stack = new ArrayDeque<>();
        TreeNode node = root;
        while (!stack.isEmpty() || node != null) {
            while (node != null) {
                stack.push(node);
                node = node.left;
            }
            node = stack.pop();
            result.add(node.val);
            node = node.right;
        }

        return result;
    }
}

3.后序遍历

方法一:单栈(类似前序遍历)

//后序遍历顺序:左-右-中 入栈顺序:中-左-右 出栈顺序:中-右-左, 最后翻转结果
class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        if (root == null) {
            return result;   
        }

        Deque<TreeNode> stack = new ArrayDeque<>();
        stack.push(root);

        TreeNode temp = new TreeNode();
        while (!stack.isEmpty()) {
            temp = stack.pop();
            result.add(temp.val);
            if (temp.left != null) {
                stack.push(temp.left);
            }
            if (temp.right != null) {
                stack.push(temp.right);
            }
        }
        
        Collections.reverse(result);
        return result;
    }
}

方法二:单栈(哨兵)

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        if (root == null) {
            return result;
        }

        Deque<TreeNode> stack = new ArrayDeque<>();
        TreeNode node = root;
        TreeNode pre = null;

        while (node != null || !stack.isEmpty()) {
            while (node != null) {
                stack.push(node);
                node = node.left;
            }

            node = stack.peek();
            if (node.right == null || node.right == pre) {
                stack.pop();
                result.add(node.val);
                pre = node;
                node = null;
            } else {
                node = node.right;
            }
        }

        return result;
    }
}

方法三:双栈

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        if (root == null) {
            return result;
        }

        Deque<TreeNode> stack = new ArrayDeque<>();
        Deque<TreeNode> resStack = new ArrayDeque<>();
        TreeNode node = root;
        while(node != null || !stack.isEmpty()) {
            if (node != null) {
                stack.push(node);
                resStack.push(node);
                node = node.right;
            } else {
                node = stack.pop();
                node = node.left;
            }
        }
        
        while (!resStack.isEmpty()) {
            result.add(resStack.pop().val);
        }

        return result;
    }
}

三.Morris 遍历

Morris 遍历的核心思想是利用树的大量空闲指针,实现空间开销的极限缩减。

1.前序遍历

其前序遍历规则总结如下:

  1. 新建临时节点,令该节点为p1;
  2. 如果当前节点p1的左子节点为空,将当前节点p1加入答案,并遍历当前节点p1的右子节点;
  3. 如果当前节点p1的左子节点不为空,在当前节点p1的左子树中找到当前节点p1在中序遍历下的前驱节点(左树最右节点)p2:
  • 如果前驱节点p2的右子节点为空,将前驱节点p2的右子节点设置为当前节点p1。然后将当前节点p1加入答案,并将前驱节点p2的右子节点更新为当前节点p1(p2.right == p1)。当前节点p1更新为当前节点p1的左子节点。
  • 如果前驱节点p2的右子节点为当前节点p1,将它p2的右子节点重新设为空。当前节点p1更新为当前节点p1的右子节点。
  1. 重复步骤 2 和步骤 3,直到遍历结束。

  Morris其实解决了一个常规循环中循环到叶子节点后难以回到根节点的问题。 我们都知道前序遍历是先左后右,那么对任一节点p1来说,其右子树p1right所有节点必然在左子树p1left之后。代码中第二个while做的是,在p1left里一直往右,直到找不到更右的点,记这一点为p2。然后把p1right接到p2的右边。 这样既保证了p1right在p1left所有点之后,又不需要再回到p1节点。 即在正常的往下循环的过程中,不断把右半部分剪下来,接到左半部分的最右下

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
        if (root == null) {
            return res;
        }

        TreeNode p1 = root, p2 = null;

        while (p1 != null) {
            p2 = p1.left;
            
            if (p2 != null) {
                while (p2.right != null && p2.right != p1) {
                    p2 = p2.right;
                }
                if (p2.right == null) {
                    res.add(p1.val);
                    p2.right = p1;
                    p1 = p1.left;
                    continue;
                } else {
                    p2.right = null;
                }
            } else {
                res.add(p1.val);
            }
            
            p1 = p1.right;
        }
        return res;
    }
}

2.中序遍历

Morris 遍历算法整体步骤如下(假设当前遍历到的节点为 xx):

  1. 如果 x 无左孩子,先将 x 的值加入答案数组,再访问 x 的右孩子,即 x = x.right。
  2. 如果 x 有左孩子,则找到 x 左子树上最右的节点(即左子树中序遍历的最后一个节点,x 在中序遍历中的前驱节点),我们记为 predecessor。根据 predecessor 的右孩子是否为空,进行如下操作。
  • 如果predecessor 的右孩子为空,则将其右孩子指向 x,然后访问 x 的左孩子,即 x = x.left。
  • 如果 predecessor 的右孩子不为空,则此时其右孩子指向 x,说明我们已经遍历完 x 的左子树,我们将 predecessor 的右孩子置空,将 x 的值加入答案数组,然后访问 x 的右孩子,即 x =x.right。
  1. 重复上述操作,直至访问完整棵树。
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
        TreeNode predecessor = null;

        while (root != null) {
            if (root.left != null) {
                // predecessor 节点就是当前 root 节点向左走一步,然后一直向右走至无法走为止
                predecessor = root.left;
                while (predecessor.right != null && predecessor.right != root) {
                    predecessor = predecessor.right;
                }
                
                // 让 predecessor 的右指针指向 root,继续遍历左子树
                if (predecessor.right == null) {
                    predecessor.right = root;
                    root = root.left;
                }
                // 说明左子树已经访问完了,我们需要断开链接
                else {
                    res.add(root.val);
                    predecessor.right = null;
                    root = root.right;
                }
            }
            // 如果没有左孩子,则直接访问右孩子
            else {
                res.add(root.val);
                root = root.right;
            }
        }
        return res;
    }
}

3.后序遍历

其后序遍历规则总结如下:

  1. 新建临时节点,令该节点为 root;
  2. 如果当前节点的左子节点为空,则遍历当前节点的右子节点;
  3. 如果当前节点的左子节点不为空,在当前节点的左子树中找到当前节点在中序遍历下的前驱节点;
  • 如果前驱节点的右子节点为空,将前驱节点的右子节点设置为当前节点,当前节点更新为当前节点的左子节点。
  • 如果前驱节点的右子节点为当前节点,将它的右子节点重新设为空。倒序输出从当前节点的左子节点到该前驱节点这条路径上的所有节点。当前节点更新为当前节点的右子节点。
  1. 重复步骤 2 和步骤 3,直到遍历结束。
class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
        if (root == null) {
            return res;
        }

        TreeNode p1 = root, p2 = null;

        while (p1 != null) {
            p2 = p1.left;
            if (p2 != null) {
                while (p2.right != null && p2.right != p1) {
                    p2 = p2.right;
                }
                if (p2.right == null) {
                    p2.right = p1;
                    p1 = p1.left;
                    continue;
                } else {
                    p2.right = null;
                    addPath(res, p1.left);
                }
            }
            p1 = p1.right;
        }
        addPath(res, root);
        return res;
    }

    public void addPath(List<Integer> res, TreeNode node) {
        int count = 0;
        while (node != null) {
            ++count;
            res.add(node.val);
            node = node.right;
        }
        int left = res.size() - count, right = res.size() - 1;
        while (left < right) {
            int temp = res.get(left);
            res.set(left, res.get(right));
            res.set(right, temp);
            left++;
            right--;
        }
    }
}

四.层次遍历

迭代(广度优先遍历BFS)

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> res = new ArrayList<>();
        if (root == null) {
            return res;
        }

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

递归(深度优先遍历DFS)

class Solution {
    List<List<Integer>> res = new ArrayList<>();

    public List<List<Integer>> levelOrder(TreeNode root) {
        dfs(root, 0);
        return res;
    }

    public void dfs(TreeNode node, int depth) {
        if (node == null) {
            return;
        }
        depth++;

        if (res.size() < depth) {
            res.add(new ArrayList<Integer>());
        }

        res.get(depth - 1).add(node.val);
        dfs(node.left, depth);
        dfs(node.right, depth);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值