二叉树非递归遍历的通用解法

最近在跟慕课网上《玩转算法面试》这门课,老师讲的很不错,其中有一节提到了用栈来模拟二叉树的前序,中序,后序遍历。
二叉树的遍历的递归解法很简单,但有时面试或做题时往往会要求写出非递归的形式,一般教科书上实现的前序,中序和后序遍历的非递归形式差别都比较大,特别是后序遍历,很难理解。那有没有一种通用的迭代解法能适于这三种遍历呢?下面介绍一下这种通用解法。

基本思想

先给出前序遍历的递归形式代码如下:

public void preorder(TreeNode node) {
	if(node != null) {
		System.out.println(node.val);
		preorder(node.left);
		preorder(node.right);
	}
}

假设有这样一颗二叉树:

上述递归代码中其实只包含三条指令:打印结点,访问左孩子,访问右孩子,每条指令用一种命令来表示:

由于栈具有后进先出的特性,所以上述递归代码等价于向栈中推入三条命令:

上述是遍历结点1的情况,首先打印结点即count 1出栈,然后go 1-L出栈递归访问其左孩子结点2,对于结点2,也会有如上三条命令,将其也按照待访问顺序加入栈中:

依次执行上述过程直到栈为空为止。

前序遍历非递归代码

根据上述过程,我们将其写成代码,可以用一个Command类来表示一条命令,其代码如下:

private class Command {
	String s;    // go, print
    TreeNode node;
    Command(String s, TreeNode node) {
    	this.s = s;
        this.node = node;
    }
}

其中print代表打印结点的命令,go代表递归调用。
整个前序遍历非递归的代码如下:

    private class TreeNode {
        int val;
        TreeNode left;
        TreeNode right;
        TreeNode(int x) { val = x; }
    }

    private class Command {
        String s;    // go, print
        TreeNode node;
        Command(String s, TreeNode node) {
            this.s = s;
            this.node = node;
        }
    }
	public void preorderTraversal(TreeNode root) {
	 	Stack<Command> st = new Stack<>();
        st.add(new Command("go", root));

        while(!st.isEmpty()) {
            Command cd = st.pop();
            if(cd.s.equals("print")) {
                System.out.println(cd.node.val)
            } else {
                assert cd.s.equals("go");
                if(cd.node.right != null) {
                    st.add(new Command("go", cd.node.right));
                }
                if(cd.node.left != null) {
                    st.add(new Command("go", cd.node.left));
                }
                st.push(new Command("print", cd.node));
            }
        }
	}

写出了前序遍历的非递归代码后,中序和后序遍历就比较简单了,只需要交换一下入栈的顺序即可。

中序非递归代码

	public void inorderTraversal(TreeNode root) {
	 	Stack<Command> st = new Stack<>();
        st.add(new Command("go", root));

        while(!st.isEmpty()) {
            Command cd = st.pop();
            if(cd.s.equals("print")) {
                System.out.println(cd.node.val)
            } else {
                assert cd.s.equals("go");
                if(cd.node.right != null) {
                    st.add(new Command("go", cd.node.right));
                }
                st.push(new Command("print", cd.node));
                if(cd.node.left != null) {
                    st.add(new Command("go", cd.node.left));
                }
            }
        }
	}

可以看到只是将st.push(new Command("print", cd.node));放到了中间。

后序非递归代码

	public void postorderTraversal(TreeNode root) {
	 	Stack<Command> st = new Stack<>();
        st.add(new Command("go", root));

        while(!st.isEmpty()) {
            Command cd = st.pop();
            if(cd.s.equals("print")) {
                System.out.println(cd.node.val)
            } else {
                assert cd.s.equals("go");
                st.push(new Command("print", cd.node));
                if(cd.node.right != null) {
                    st.add(new Command("go", cd.node.right));
                }
                if(cd.node.left != null) {
                    st.add(new Command("go", cd.node.left));
                }
            }
        }
	}

前序,中序和后序非递归遍历的整个形式保持一致,非常好记。可以通过LeetCode上的144145题去实践一下。

参考:慕课网《玩转算法面试》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值