二叉树的非递归遍历(前序、中序、后序多种方法)

一、深度优先-广度优先统一的方式

由于常见的深度优先遍历是用递归的方式,而常见的广度优先遍历是用迭代的方式,所以无法体现出二者的内在联系,也就无法将二者统一。
事实上,只要用非递归(迭代)的方式实现深度优先遍历,即可将深度优先遍历与广度优先遍历统一起来,进而也就可以将几种深度优先遍历的方式(前序、中序、后序)也一起统一起来。

非递归的广度优先遍历

首先,广度优先遍历二叉树需要一个辅助队列,遍历方式如下:

// 二叉树的广度优先遍历
public List<Integer> breadthFirstSearch(TreeNode root) {
    List<Integer> resultList = new ArrayList<>();  // 结果列表
    Deque<TreeNode> queue = new LinkedList<>();  // 辅助队列,其中存储的是(本层和下一层中)尚未遍历的节点
    if (root != null) queue.offer(root);  // 根节点如果存在,就先入队
    while (!queue.isEmpty()) {  // 队列不为空就说明还有节点未遍历到
        // 取出队列中下一个要遍历的节点(也就是队头的节点)
        TreeNode node = queue.poll();
        // 保存结果
        resultList.add(node.val);
        // 如果该节点有左、右节点,就入队
        if (node.left != null) queue.offer(node.left);
        if (node.right != null) queue.offer(node.right);
    }
    return resultList;
}

非递归的深度优先遍历

最简单的深度优先遍历是递归的方式,但是如何实现非递归的深度优先遍历呢?

很有趣的是,只需要把广度优先遍历中的队列换成栈,然后把左、右子节点的处理顺序对调,就变成了深度优先遍历!
除此之外,一字不改!

修改之后的遍历如下:

// 二叉树的深度优先遍历(前序遍历)
public List<Integer> preorderTraversal(TreeNode root) {
    List<Integer> resultList = new ArrayList<>();  // 结果列表
    Deque<TreeNode> stack = new LinkedList<>();  // 辅助栈
    if (root != null) stack.offer(root);  // 根节点如果存在,就先入栈
    while (!stack.isEmpty()) {  // 栈不为空就说明还有节点未遍历到
        // 取出栈中下一个要遍历的节点(即按前序的下一个节点)
        TreeNode node = stack.pop();
        // 保存结果
        resultList.add(node.val);
        // 如果该节点有左、右节点,就入栈(注意,要先右后左)
        if (node.right != null) stack.push(node.right);  // 先右!
        if (node.left != null) stack.push(node.left);  // 后左!
    }
    return resultList;
}

由此,广度优先遍历与深度优先遍历完成了大一统!很有数学之美!

非递归的前序遍历

上面的深度优先遍历就是前序遍历!

非递归的后序遍历

那么前序遍历和后序遍历如何进行统一呢?
其实也很简单,只需要将左、右子节点的处理顺序换回到正常顺序,再把最终结果序列反转,就直接 OK 了!

// 二叉树的后序遍历
public List<Integer> postorderTraversal(TreeNode root) {
    List<Integer> resultList = new ArrayList<>();  // 结果列表
    Deque<TreeNode> stack = new LinkedList<>();  // 辅助栈
    if (root != null) stack.offer(root);  // 根节点如果存在,就先入栈
    while (!stack.isEmpty()) {  // 栈不为空就说明还有节点未遍历到
        // 取出栈中下一个要遍历的节点(即按前序的下一个节点)
        TreeNode node = stack.pop();
        // 暂时保存结果(此时结果是倒序的)
        resultList.add(node.val);
        // 如果该节点有左、右节点,就入队(先左后右的正常顺序)
        if (node.left != null) stack.push(node.left);
        if (node.right != null) stack.push(node.right);
    }
    // 将倒序的结果反转为正序的结果
    int size = resultList.size(), temp;
    for (int i = 0; i < size >> 1; i++) {
        temp = resultList.get(i);
        resultList.set(i, resultList.get(size - i - 1));
        resultList.set(size - i - 1, temp);
    }
    return resultList;
}

二、辅助栈存储深度优先遍历路径的方式

这种方式是利用一个辅助栈存储按深度优先遍历走过的路径上的、但是按要求的顺序还不应该被输出的节点,也可以很好的将前序、中序、后序三种遍历方式进行统一。
三种方式的注意区别就在于保存结果的时机不同。

非递归的前序遍历

// 二叉树的前序遍历
public List<Integer> preorderTraversal1(TreeNode root) {
    List<Integer> resultList = new ArrayList<>();  // 结果列表
    Deque<TreeNode> stack = new LinkedList<>();  // 辅助栈
    while (!stack.isEmpty() || root != null) {  // 栈不为空或者当前节点存在
        // 先冲到左下角,并一路入栈 + 保存结果
        while (root != null) {
            stack.push(root);
            resultList.add(root.val);
            root = root.left;
        }
        // 弹出栈顶节点
        root = stack.pop();
        // 指向当前节点的右孩子
        root = root.right;
    }
    return resultList;
}

非递归的中序遍历

// 二叉树的中序遍历
public List<Integer> inorderTraversal1(TreeNode root) {
    List<Integer> resultList = new ArrayList<>();  // 结果列表
    Deque<TreeNode> stack = new LinkedList<>();  // 辅助栈
    while (!stack.isEmpty() || root != null) {  // 栈不为空或者当前节点存在
        // 先冲到左下角,并一路入栈
        while (root != null) {
            stack.push(root);
            root = root.left;
        }
        // 弹出栈顶节点
        root = stack.pop();
        // 保存结果
        resultList.add(root.val);
        // 指向当前节点的右孩子
        root = root.right;
    }
    return resultList;
}

非递归的后序遍历

// 二叉树的后序遍历
public List<Integer> postorderTraversal1(TreeNode root) {
    List<Integer> resultList = new ArrayList<>();  // 结果列表
    Deque<TreeNode> stack = new LinkedList<>();  // 辅助栈
    TreeNode preNode = null;  // 用于防止遍历时重复进入节点的右子节点,如果右子节点刚去过,就视为没有
    while (!stack.isEmpty() || root != null) {  // 栈不为空或者当前节点存在
        // 先冲到左下角,并一路入栈
        while (root != null) {
            stack.push(root);
            root = root.left;
        }
        // 分情况处理栈顶节点
        assert stack.peek() != null;
        if (stack.peek().right == null || stack.peek().right == preNode) {  // 第二个条件是为了防止重复进入右子节点,即如果右子节点刚去过,就视为没有
            root = stack.pop();  // 弹出栈顶节点
            resultList.add(root.val);  // 保存结果
            preNode = root;  // 更新 preNode
            root = null;  // 指向 null
        } else {
            root = stack.peek();  // 指向栈顶节点
            root = root.right;  // 指向当前节点的右孩子
        }
    }
    return resultList;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZBH4444

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值