标签:
接着上一篇 二叉树的先序-中序-后序遍历(一)-递归 的讲,这篇该循环遍历了。
之前一直没有找到好的方法来循环遍历树,以前我老认为有些递归的能做的东西很难换成循环实现。
后来看了一些别人写的代码,然后又问了朋友,才发现。。。哦,原来这样的啊,我可以自己弄个栈来维护一下。
想到了可以弄个栈以后,至少在我认为,把递归转成循环已经是可行的了,至于怎么实现,这几天在想(因为太笨,看人家的代码总看一半就不想看),今天找到了一个好用的方法,在这里分享一下。
算法的核心是:你看二叉树的时候心里怎么想的,程序就怎么写。。。。
一、先序遍历
1.想法:首先,明确步骤:对于单个节点:中-左-右。
然后画一棵树,还是上一篇那棵(我也懒得找其他例子),摆在面前对着看。
假定现在当前节点是10,第一个数字肯定是10。
然后看左边:值为6,那不管,先把6算上。
再看6,左边是4,也不管,先把4算上。
4左边右边都没了,那只能往回走了,回到6,看6的右边,看到8,不管,先把8算上。
8左边右边都没了,那就先结束把。
在刚才遍历的过程中,用到了多次6,所以我们可以把6找一个容器存放起来。再看整棵数:6,10,14都要存放起来,还是一个后进先出的结构,那我们就借助一个栈。
然后是这个6比如说读到2次,总不能说每次读到都算一个把,我看网上有的方法是用一个键值对的标记来记住某个元素是否已经读过了。但是我怕麻烦,一麻烦就老错,错了就烦,,所以这里用一个自己想的方法:可以看到从10到6的时候读了6一次,从4或者8到6都不读,所以这里我定义从上往下走的时候读,其他时候不读。
2.重温
带着这种思路再从6开始走一遍:
(从10到6,这个时候看到6,一看是从上往下的:读,然后把6放到栈中),然后看6的左边;
看到4,也是从上往下的:读,然后把4放入栈中,然后看4的左边;
没有,那就看4的右边;
没有,4从栈中弹出,这个时候回到6了;
这个时候的6不是从上往下读到的,所以不管;
当前是6,因为是从左子到上,所以不可能再走左下,应该走右下;
右下为8:读,然后把8放入栈中,再看8的左下;
左下为空,再看右下,也为空,弹出8;
回到6,现在左右都读过了,那只能弹出6;
栈空,结束。
3.小结
核心:分三种情况考虑:
1.从上往下读
2.从左子树往上读
3.从右子树往上读
4.看代码:
(看到方法就先试图通过方法名猜出方法是干嘛的,等一口气看完了所有再自己看方法的实现)
// //////////////==循环 // 循环遍历,先序遍历 public static void readTreeByLoopFirst(TreeNode root, Queue<TreeNode> to) { Stack<TreeNode> nodeContainer = new Stack<TreeNode>(); TreeNode previousTemp = null; TreeNode current = root; while (current != null) { // 如果是从上边来的 if (isNotFromLower(current, previousTemp)) { to.offer(current); } // 走左子 if (current.left != null && isNotFromLower(current, previousTemp)) { previousTemp = current; nodeContainer.push(current); current = current.left; // 走右子 } else if (current.right != null && isNotFromRightLower(current, previousTemp)) { previousTemp = current; nodeContainer.push(current); current = current.right; // 走上:咋走?从栈拿 } else { if (nodeContainer.isEmpty()) { break; } previousTemp = current; current = nodeContainer.pop(); } } } /** * 判断此次流程是不是从右子往上 * */ private static boolean isNotFromRightLower(TreeNode current, TreeNode previousTemp) { // 如果右边不为空,切右边等于记录,说明就是从右子往上 if (current.right != null && current.right == previousTemp) { return false; } return true; } /** * 判断此次流程是不是从下往上走 * */ private static boolean isNotFromLower(TreeNode current, TreeNode previousTemp) { // 如果左边不为空,且左边等于记录,说明就是从左子往上 if (current.left != null && current.left == previousTemp) { return false; } // 如果右边不为空,切右边等于记录,说明就是从右子往上 if (current.right != null && current.right == previousTemp) { return false; } return true; }
每次循环都做这么几件事情:
1.(由于这里是先序遍历)是不是往下走的?是:读这个数。不是:没有他事
2.判断下一步改怎么走:
1),是不是要往左下走:那得是首先要有左下的节点,然后还得是当前这步不是从左下或者右下过来的,只能是从上下来的。
2),是不是要往右下走:那得是首先要有右下的节点,然后还得是当前这步不是从右下走过来的,外加经过1的筛选。
3),剩下的就是往上走:这个树只有left,right,那就用栈来实现,弹出一个,就可以往上了。
3.具体走下一步要做的事:
1),所有的步骤都得记录之前一步走的是哪:previousTemp = current
2),如果是往下走的,得压栈,如果是往上走的,得弹出栈。
3),将新的值赋值给current节点,以便循环继续。
二、中序遍历
// 循环遍历,中序遍历 public static void readTreeByLoopMiddle(TreeNode root, Queue<TreeNode> to) { Stack<TreeNode> nodeContainer = new Stack<TreeNode>(); TreeNode previousTemp = null; TreeNode current = root; while (current != null) { // 如果是从左边上来的 if (current.left == null || isFromLeftLower(current, previousTemp)) { to.offer(current); } // 走左子 if (current.left != null && isNotFromLower(current, previousTemp)) { previousTemp = current; nodeContainer.push(current); current = current.left; // 走右子 } else if (current.right != null && isNotFromRightLower(current, previousTemp)) { previousTemp = current; nodeContainer.push(current); current = current.right; // 走上:咋走?从栈拿 } else { if (nodeContainer.isEmpty()) { break; } previousTemp = current; current = nodeContainer.pop(); } } } private static boolean isFromLeftLower(TreeNode current, TreeNode previousTemp) { return (current.left != null && current.left == previousTemp); } /** * 判断此次流程是不是从右子往上 * */ private static boolean isNotFromRightLower(TreeNode current, TreeNode previousTemp) { // 如果右边不为空,切右边等于记录,说明就是从右子往上 if (current.right != null && current.right == previousTemp) { return false; } return true; } /** * 判断此次流程是不是从下往上走 * */ private static boolean isNotFromLower(TreeNode current, TreeNode previousTemp) { // 如果左边不为空,且左边等于记录,说明就是从左子往上 if (current.left != null && current.left == previousTemp) { return false; } // 如果右边不为空,切右边等于记录,说明就是从右子往上 if (current.right != null && current.right == previousTemp) { return false; } return true; }
三、后序遍历
// 循环遍历,后序遍历 public static void readTreeByLoopEnd(TreeNode root, Queue<TreeNode> to) { Stack<TreeNode> nodeContainer = new Stack<TreeNode>(); TreeNode previousTemp = null; TreeNode current = root; while (current != null) { // 如果是从右边来的 if (current.right == null || (current.right != null && current.right == previousTemp)) { to.offer(current); } // 走左子 if (current.left != null && isNotFromLower(current, previousTemp)) { previousTemp = current; nodeContainer.push(current); current = current.left; // 走右子 } else if (current.right != null && isNotFromRightLower(current, previousTemp)) { previousTemp = current; nodeContainer.push(current); current = current.right; // 走上:咋走?从栈拿 } else { if (nodeContainer.isEmpty()) { break; } previousTemp = current; current = nodeContainer.pop(); } } } /** * 判断此次流程是不是从右子往上 * */ private static boolean isNotFromRightLower(TreeNode current, TreeNode previousTemp) { // 如果右边不为空,切右边等于记录,说明就是从右子往上 if (current.right != null && current.right == previousTemp) { return false; } return true; } /** * 判断此次流程是不是从下往上走 * */ private static boolean isNotFromLower(TreeNode current, TreeNode previousTemp) { // 如果左边不为空,且左边等于记录,说明就是从左子往上 if (current.left != null && current.left == previousTemp) { return false; } // 如果右边不为空,切右边等于记录,说明就是从右子往上 if (current.right != null && current.right == previousTemp) { return false; } return true; }
四、总结
跟着想法一步步走来,遇到了很多小问题,通过单元测试和debug找到其中的bug,然后不断完善,形成一个比较通用的方法,可以看到不管怎么遍历,就改一行代码就行。
当然程序中可能还存在bug,请各位帮忙纠正了。。。
版权声明:本文为博主原创文章,未经博主允许不得转载。
二叉树的先序-中序-后序遍历(一)-循环----绝对白痴好记的方法
标签:
原文地址:http://blog.csdn.net/q291611265/article/details/47759785