二叉树的深度优先遍历,前、中、后序遍历的递归和非递归实现

62 篇文章 0 订阅

参考连接:一篇文看透二叉树,先序遍历、中序遍历、后序遍历、广度优先、深度优先,java实现

1 递归实现

1.1 前序遍历

前序遍历的顺序是:root->left->right
使用递归的形式很简单,但是因为使用了递归,所以多做了很多重复的工作,使得代码复杂度很高,但是代码简单易读。

	public void preOrderTrversal(TreeNode root){
	    if (root!=null){
	         System.out.print(root.getVal()+"  ");
	         preOrderTrversal(root.getLeft());
	         preOrderTrversal(root.getRight());
	     }
	}

1.2 中序遍历

中序遍历的顺序是:left->root->right

	public List<Integer> midOrderTrversal(TreeNode root){
        List<Integer> res = new LinkedList<>();
        if (root!=null){
            midOrderTrversal(root.getLeft());
            System.out.print(root.getVal()+"  ");
            midOrderTrversal(root.getRight());
        }
        return res;
    }

1.3 后序遍历

后序遍历的顺序是:left->right->root

	public void afterOrderTrversal(TreeNode root){
        if (root!=null){
            afterOrderTrversal(root.getLeft());
            afterOrderTrversal(root.getRight());
            System.out.print(root.getVal()+"  ");
        }
    }

1.4 案例在这里插入图片描述

以上面这颗树为例,下面是三种遍历结果的序列:
在这里插入图片描述

2 非递归实现

上面有讲过,递归实现的算法,虽然代码简单清晰,但是过程中,多做了很多无畏的计算,增加了代码的复杂度。下面介绍一下非递归的实现方式。

2.1 前序遍历

前序遍历的顺序是:root->left->right

  1. 思路1

基本思路:使用栈替代递归,因为递归在电脑系统中也是栈的一种应用。前序遍历相对较简单,任何节点都可以看成是一个根节点,所以没遇到一个节点先直接输出,在将当前节点的右子树先放入栈中,再将左子树放入栈中(因为栈式先入后出),直到最后一个节点被输出完毕即可。

	public void preOrder(TreeNode root) {
        if (null == root)
            return;
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);
        while (!stack.isEmpty()) {
            TreeNode t = stack.pop();
            System.out.print(t.val + "  ");
            if (t.right != null) {
                stack.push(t.right);
            }
            if (t.left != null) {
                stack.push(t.left);
            }
        }
        System.out.println();
    }
  1. 思路2

对于前序遍历,还有第二种思路,就是先把当前二叉树的根节点和左子树添加到栈中,如果当前节点没有左子树了,那么久退出当前的循环,并将当前节点退回到上一步,级当前节点的父节点,然后访问其父节点的右子树。

	public void preOrder2(TreeNode root) {
        if (null == root)
            return;
        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;
        while (null != cur || !stack.empty()) {
            while (null != cur) { //先把根和左子树遍历并添加到栈
                System.out.print(cur.val + "  ");
                stack.push(cur);
                cur = cur.left;
            }
            if (!stack.empty()) { //当cur没有左子树时,此时需要出栈,进入到右子树的循环中
                cur = stack.pop();
                cur = cur.right;
            }
        }
    }

2.2 中序遍历

中序遍历的顺序是:left->root->right
中序遍历和前序遍历有些类似,只需要将输出位置做一个简单的调整即可。因为中序遍历是左子树先被访问,然后才是根节点被访问。

	public void inOrder(TreeNode root) {
        if (null == root) {
            return;
        }
        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;
        while (cur != null || !stack.empty()) {
            while (cur != null) {
                stack.push(cur);
                cur = cur.left;
            }

            if (!stack.empty()) {
                cur = stack.pop();//此处被弹出的是左子树
                System.out.print(cur.val + "  ");//输出位置放到这里了
                cur = cur.right;
            }
        }
        System.out.println();
    }

2.3 后序遍历

后序遍历的顺序是:left->right->root
(1)方法一
后序遍历相对前序和中序有点复杂,因为根节点是在左右子树都被访问过之后才能被访问,所以需要对上一次被访问的节点进行标记,如果上一次访问的是左子树,则继续遍历右子树,如果上一次访问的是右子树,则输出根节点的值;还有一种情况就是当前节点没有右子树,也是直接输出根节点的值。

	public void postOrder(TreeNode root) {
        if (null == root)
            return;
        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;
        TreeNode lastVisited = null;

        while (null != cur) { //从根节点出发,将该二叉树的所有左子树都放入栈中
            stack.push(cur);
            cur = cur.left;
        }

        while (!stack.empty()) {
            cur = stack.pop();
            if (cur.right == null || lastVisited == cur.right) { //如果当前根节点右子树为空或者右子树被访问过  则输出当前根节点的值
                System.out.print(cur.val + "  ");
                lastVisited = cur;
            } else { //否则,需要遍历右子树
                stack.push(cur); //将当前节点放入栈中,因为还没有被访问
                cur = cur.right; //节点移动到当前节点的右子树上
                while (null!=cur){  // 对右子树做一次相同的事情
                    stack.push(cur);
                    cur =cur.left;
                }
            }
        }
        System.out.println();
    }

(2)方法二
后序遍历和前序遍历可以看成是两个相反的遍历过程,只不过后序遍历将左右子树的顺序换了一下,后序遍历的顺序为:left->right->root,而前序遍历的顺序为:root->left->right。可以看出他们其实是两个相反的过程,所以可以使用前序遍历的方法进行后序遍历,只不过遍历后的结果需要进行逆序输出。

public List<Integer> postOrder(TreeNode root) {
    if (null == root)
        return;
    Stack<TreeNode> stack = new Stack<>();
    stack.push(root);
    List<Integer> res = new ArrayList<>();
    while (!stack.isEmpty()) {
        TreeNode t = stack.pop();
        if (t.left != null) {
            stack.push(t.left);
        }
        if (t.right != null) {
            stack.push(t.right);
        }
        res.add(0, t.val); //逆序输出
    }
    return res;
}

2.4 非递归完整代码

package middle;

import kotlin.reflect.jvm.internal.impl.util.collectionUtils.ScopeUtilsKt;
import utils.tree.TreeNode;

import java.util.Stack;

/**
 * 用非递归的方式实现树的三种深度优先遍历
 */
public class DfsStack {
    public void preOrder2(TreeNode root) {
        if (null == root)
            return;
        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;
        while (null != cur || !stack.empty()) {
            while (null != cur) { //先把根和左子树遍历并添加到栈
                System.out.print(cur.val + "  ");
                stack.push(cur);
                cur = cur.left;
            }
            if (!stack.empty()) { //当cur没有左子树时,此时需要出栈,进入到右子树的循环中
                cur = stack.pop();
                cur = cur.right;
            }
        }
    }

    public void preOrder(TreeNode root) {
        if (null == root)
            return;
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);
        while (!stack.isEmpty()) {
            TreeNode t = stack.pop();
            System.out.print(t.val + "  ");
            if (t.right != null) {
                stack.push(t.right);
            }
            if (t.left != null) {
                stack.push(t.left);
            }
        }
        System.out.println();
    }

    public void inOrder(TreeNode root) {
        if (null == root) {
            return;
        }
        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;
        while (cur != null || !stack.empty()) {
            while (cur != null) {
                stack.push(cur);
                cur = cur.left;
            }

            if (!stack.empty()) {
                cur = stack.pop();
                System.out.print(cur.val + "  ");
                cur = cur.right;
            }
        }
        System.out.println();
    }

    /**
     * 此种方法输出的是后序遍历的相反的序列,将其反向即是正确的遍历顺序
     * @param root
     */
    
    public void afterOrder(TreeNode root) {
        if (null == root)
            return;
        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;
        stack.push(cur);
        while (cur!=null||!stack.isEmpty()) {
            while (cur!=null){
                stack.push(cur);
                cur = cur.left;
            }
            if (!stack.empty()){
                TreeNode t = stack.pop();
                System.out.print(t.val+"  ");
                cur = cur.right;
            }
        }
        System.out.println();
    }

    public void afterOrder2(TreeNode root) {
        if (null == root)
            return;
        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;
        TreeNode lastVisited = null;

        while (null != cur) { //从根节点出发,将该二叉树的所有左子树都放入栈中
            stack.push(cur);
            cur = cur.left;
        }

        while (!stack.empty()) {
            cur = stack.pop();
            if (cur.right == null || lastVisited == cur.right) { //如果当前根节点右子树为空或者右子树被访问过  则输出当前根节点的值
                System.out.print(cur.val + "  ");
                lastVisited = cur;
            } else { //否则,需要遍历右子树
                stack.push(cur); //将当前节点放入栈中,因为还没有被访问
                cur = cur.right; //节点移动到当前节点的右子树上
                while (null!=cur){  // 对右子树做一次相同的事情
                    stack.push(cur);
                    cur =cur.left;
                }
            }
        }
        System.out.println();
    }

    public static void main(String[] args) {
        DFS dfs = new DFS();
        DfsStack dfsStack = new DfsStack();
        System.out.println("前序遍历:");
        dfs.preOrderTrversal(root);
        System.out.println();
        dfsStack.preOrder(root);

        System.out.println("中序遍历");
        dfs.midOrderTrversal(root);
        System.out.println();
        dfsStack.inOrder(root);

        System.out.println("后序遍历:");
        dfs.afterOrderTrversal(root);
        System.out.println();
        dfsStack.afterOrder2(root);

    }

}

在这里插入图片描述
如果觉得不够详细,可以点击这里查看详细解答(非本人)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值