算法通关村—迭代实现二叉树的遍历

1.迭代法实现前序遍历

前序遍历是中左右,如果还有左子树就一直向下找。完了之后再返回从最底层逐步向上向右找。 不难写出如下代码: (注意代码中,空节点不入栈)

 /**
     * 前序遍历中左右
     *
     * @param root 根节点
     * @return 前序遍历结果
     */
    public List<Integer> preOrderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
        if (root == null) {
            return res;
        }
        Deque<TreeNode> stack = new LinkedList<>();
        TreeNode node = root;
        while (node != null || !stack.isEmpty()) {
            while (node != null) {
                res.add(node.val);
                stack.push(node);
                node = node.left;
            }
            TreeNode pop = stack.pop();
            node = pop.right;
        }
        return res;
    }

2.迭代法实现中序遍历

再看中序遍历,中序遍历是左中右,先访问的是二叉树左子树的节点,然后一层一层向下访问,直到到达树左面的最底部,再开始处理节点(也就是在把节点的数值放进res列表中)。在使用迭代法写中序遍历,就需要借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素。 看代码:

    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        if (root == null) {
            return res;
        }
        Deque<TreeNode> stack = new LinkedList<>();
        TreeNode node = root;
        while (node != null || !stack.isEmpty()) {
            while (node != null) {
                stack.push(node);
                node = node.left;
            }
            TreeNode pop = stack.pop();
            res.add(pop.val);
            node = pop.right;
        }
        return res;
    }

3.迭代法实现后序遍历

(1) 反转法

如下图,我们先观察后序遍历的结果是seq={9 5 7 4 3},如果我们将其整体反转的话就是new_seq={3 4 7 5 9}。

在这里插入图片描述

而之前的前序遍历思路跟此处的大概一致,只是前序遍历是中左右的顺序,这里是中右左,稍作调整即可,代码如下

public  List<Integer> postOrderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        if (root == null) return res;
        Stack<TreeNode> stack = new Stack<>();
        TreeNode node = root;
        while (!stack.isEmpty() || node != null) {
            while (node != null) {
                res.add(node.val);
                stack.push(node);
                node = node.right;
            }
            node = stack.pop();
            node = node.left;
        }
        Collections.reverse(res);
        return res;
    }

最后我们使用 Collections.reverse(res)可以结果数组反转得到正确结果

(2) 访问标记法

在leetcode找了一个gif图,主要展示起元素的移动过程

在这里插入图片描述

  • 首先先找到树的最深左子节点,并在其过程中依次入栈
  • 到最深处后开始出栈,出栈后判断右子节点是否为null或者为上一个标记的结点,如果符合条件则把当前节点的值加到结果集里,并将当前节点赋值给prev标记,否则当前节点继续入栈,并且跳到右子节点
  • 以此循环获得最终结果

代码实现如下:

/**
     * 后序遍历左右中
     *
     * @param root 根节点
     * @return 后序遍历结果
     */
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        if (root == null) {
            return res;
        }
        Deque<TreeNode> stack = new LinkedList<>();
        TreeNode node = root;
        TreeNode prev = null;
        while (node != null || !stack.isEmpty()) {
            while (node != null) {
                // 先找到树的最深左子节点,并在其过程中依次入栈
                stack.push(node);
                node = node.left;
            }
            node = stack.pop();
            // 判断右子节点是否为null或者为上一个标记的结点
            if (node.right == null || node.right == prev) {
                res.add(node.val);
                // 当前节点赋值给prev标记
                prev = node;
                node = null;
            } else {
                stack.push(node);
                node = node.right;
            }
        }
        return res;
    }

复杂度分析

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

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

(3)Morris 遍历

有一种巧妙的方法可以在线性时间内,只占用常数空间来实现后序遍历。这种方法由 J. H. Morris 在 1979 年的论文「Traversing Binary Trees Simply and Cheaply」中首次提出,因此被称为 Morris 遍历。

Morris 遍历的核心思想是利用树的大量空闲指针,实现空间开销的极限缩减。其后序遍历规则总结如下:

  1. 新建临时节点,令该节点为 root;

  2. 如果当前节点的左子节点为空,则遍历当前节点的右子节点;

  3. 如果当前节点的左子节点不为空,在当前节点的左子树中找到当前节点在中序遍历下的前驱节点;

    • 如果前驱节点的右子节点为空,将前驱节点的右子节点设置为当前节点,当前节点更新为当前节点的左子节点。

    • 如果前驱节点的右子节点为当前节点,将它的右子节点重新设为空。倒序输出从当前节点的左子节点到该前驱节点这条路径上的所有节点。当前节点更新为当前节点的右子节点。

重复步骤 2 和步骤 3,直到遍历结束。

这样我们利用 Morris 遍历的方法,后序遍历该二叉搜索树,即可实现线性时间与常数空间的遍历。

代码实现如下

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--;
        }
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
叉树代遍算法主要有三种:前序遍、中序遍和后序遍。下面分别介绍这三种算法实现方法: 1. 前序遍算法: 前序遍的顺序是根节点 -> 左子树 -> 右子树。使用算法实现前序遍时,可以通过栈来辅助实现。具体步骤如下: 1)将根节点入栈。 2)当栈不为空时,循环执行以下操作: a) 弹出栈顶节点,并将其值加入结果列表。 b) 若右子节点存在,则将右子节点入栈。 c) 若左子节点存在,则将左子节点入栈。 3)返回结果列表。 2. 中序遍算法: 中序遍的顺序是左子树 -> 根节点 -> 右子树。使用算法实现中序遍时,同样可以通过栈来辅助实现。具体步骤如下: 1)初始化一个空栈,并将当前节点置为根节点。 2)循环执行以下操作,直到栈为空: a) 将当前节点及其所有左子节点依次入栈,直到最左叶子节点。 b) 弹出栈顶节点,并将其值加入结果列表。 c) 若弹出节点存在右子节点,则将右子节点置为当前节点。 3)返回结果列表。 3. 后序遍算法: 后序遍的顺序是左子树 -> 右子树 -> 根节点。使用算法实现后序遍时,需要使用两个栈来辅助实现。具体步骤如下: 1)初始化两个空栈,分别为stack1和stack2。将根节点入栈stack1。 2)当stack1不为空时,循环执行以下操作: a) 弹出stack1的栈顶节点,并将其左子节点和右子节点依次入栈stack1。 b) 将弹出的节点压入stack2。 3)当stack1为空时,将stack2中的节点依次弹出并加入结果列表。 4)返回结果列表。 以上是三种常用的二叉树算法,根据不同的需求可以选择相应的算法进行实现

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值