二叉树的前序、中序、后序的非递归遍历实现
一,二叉树的遍历
二叉树的递归遍历非常简洁,递归调用需要用到栈。因此,要想实现非递归遍历,就类似于模拟程序的自动压栈、出栈,就需要创建一个栈。
本程序使用java.util.LinkedList 来表示栈。
二,前序非递归遍历实现
先序遍历是先访问该结点,再访问左子树,然后再访问右子树
因此,先访问该结点;然后将该结点入栈。(第10行)
然后,不断遍历该结点的左孩子(左左孩子....)(第8行while循环),当走到空时(第8行while不成立)。说明最“里层”的结点的左子树已经访问完毕
于是,接着访问它的左子树中的结点(第15行的 if 语句块中的第18行)。当找到它的右孩子之后,又按照前面的步骤遍历左孩子(左左孩子...)(回到第6行的大while循环,首先判断第8行的while循环)
第8行的while循环表明:结点还有左孩子...
第15行的 if 表示:结点的左孩子为空了,需要“切换”到右孩子的路径上去了(再优先访问 右孩子的 左孩子...)
1 private void nonRecurPreTraverse(BinaryNode root){ 2 LinkedList<BinaryNode> stack = new LinkedList<MyBinaryTree.BinaryNode>(); 3 BinaryNode currentNode; 4 BinaryNode tmp; 5 currentNode = root; 6 while(currentNode != null || !stack.isEmpty()) 7 { 8 while(currentNode != null)//一直往一个方向走 9 { 10 System.out.print(currentNode.ele + " ");//visit 11 stack.push(currentNode); 12 currentNode = currentNode.left; 13 } 14 15 if(!stack.isEmpty())//变换方向 16 { 17 tmp = stack.pop(); 18 currentNode = tmp.right; 19 } 20 } 21 }
前序的递归实现:(基准条件一定不能忘,这是递归的结束条件。)
1 private void preorder(BinaryNode root){ 2 if(root == null) 3 return;//base condition 4 System.out.print(root.ele + " ");//visit 5 preorder(root.left); 6 preorder(root.right); 7 }
三,中序遍历的非递归实现
先一直沿着“左孩子方向”不断地走,当走到了最左下结点时(第9行while不成立),准备出栈,访问该结点。(第15行if语句)
当出栈访问完该结点(第18、19行)之后,切换到该结点的左孩子的“子树”中,回到第6行大循环,与前面一样,继续对该“子树”先沿着“左孩子方向”不断地走....
1 private void nonRecurInTraverse(BinaryNode root){ 2 LinkedList<BinaryNode> stack = new LinkedList<BinaryNode>(); 3 BinaryNode currentNode, tmp; 4 currentNode = root; 5 6 while(currentNode != null || !stack.isEmpty()) 7 { 8 //先"走完"左孩子 9 while(currentNode != null) 10 { 11 stack.push(currentNode); 12 currentNode = currentNode.left; 13 } 14 //结点没有左孩子了,出栈,访问结点 15 if(!stack.isEmpty()) 16 { 17 tmp = stack.pop(); 18 System.out.print(tmp.ele + " ");//visit 19 currentNode = tmp.right; 20 } 21 } 22 }
中序的递归遍历:
1 private void inorder(BinaryNode root){ 2 if(root == null) 3 return; 4 inorder(root.left); 5 System.out.print(root.ele + " ");//visit 6 inorder(root.right); 7 }
四,后序遍历的非递归实现
后序遍历的非递归实现比前序、中序的非递归实现 要复杂一点。需要一个标识来标记某结点是否第一次位于栈顶(该结点的左子树已经遍历完毕,从左子树返回准备遍历它的右子树)
对于后序遍历而言,结点的左右子树都遍历完成之后,才访问该结点。某结点会两次位于栈顶,第一次是该结点的左子树都遍历完了,然后 获取 栈顶结点,切换到该结点的右孩子,准备遍历它的右子树,当该结点的右子树也都遍历完后,它就会第二次位于栈顶,此时将栈顶元素出栈。
1 private void postNonRecurTraverse(BinaryNode root){ 2 LinkedList<BinaryNode> stack = new LinkedList<MyBinaryTree.BinaryNode>(); 3 4 BinaryNode currentNode, tmp; 5 currentNode = root; 6 while(currentNode != null || !stack.isEmpty()) 7 { 8 while(currentNode != null) 9 { 10 stack.push(currentNode); 11 currentNode = currentNode.left; 12 } 13 if(!stack.isEmpty()) 14 { 15 tmp = stack.getFirst(); 16 //从左子树返回,需要判断它的右子树是否已经访问了 17 if(tmp.isFirst == false)//右子树还未被访问 18 { 19 tmp.isFirst = true; 20 currentNode = tmp.right; 21 } 22 else{//左右子树都已经访问了 23 tmp = stack.pop(); 24 System.out.print(tmp.ele + " ");//visit 25 // currentNode = null; 26 } 27 } 28 }//while 29 }
对于后序遍历而言,需要判断某个结点第一次位于栈顶,因此上面方法需要在结点类中添加一个boolean 属性表示该节点是否第一次位于栈顶。
class BinaryNode{ BinaryNode left; BinaryNode right; int ele; boolean isFirst; public BinaryNode(int ele) { this.ele = ele; left = right = null; isFirst = false; } }
五,整个程序完整实现:
buildTree()方法是根据一维整型数组 随机构造一棵二叉树。然后,对该二叉树进行各种遍历操作。关于如何构建二叉树,可参考:二叉查找树的递归实现及递归分析
import java.util.LinkedList; import java.util.Random; /** * * @author psj * */ public class MyBinaryTree { private static final Random rand = new Random();//insert left or right private static class BinaryNode{ int ele; BinaryNode left; BinaryNode right; boolean isFirst; public BinaryNode(int ele) { this.ele = ele; this.left = this.right = null; this.isFirst = false; } } private BinaryNode root;//二叉树的根结点 //随机构建二叉树 public void buildTree(){ int[] ndoes = {3,0,7,4,9,10,45}; for (int i : ndoes) { insert(i); } } public BinaryNode insert(int ele){ return root = insert(root, ele); } private BinaryNode insert(BinaryNode root, int ele){ if(root == null) return root = new BinaryNode(ele); if(rand.nextInt() %2 == 0) root.left = insert(root.left, ele); else root.right = insert(root.right, ele); return root; } //中序非递归遍历 public void nonRecurInTraverse(){ if(root == null) return; nonRecurInTraverse(root); } private void nonRecurInTraverse(BinaryNode root){ LinkedList<BinaryNode> stack = new LinkedList<BinaryNode>(); BinaryNode currentNode, tmp; currentNode = root; while(currentNode != null || !stack.isEmpty()) { // while(currentNode != null) { stack.push(currentNode); currentNode = currentNode.left; } // if(!stack.isEmpty()) { tmp = stack.pop(); System.out.print(tmp.ele + " ");//visit currentNode = tmp.right; } } } //中序递归遍历 public void inorder(){ inorder(root); } private void inorder(BinaryNode root){ if(root == null) return; inorder(root.left); System.out.print(root.ele + " ");//visit inorder(root.right); } //先序非递归遍历 public void nonRecurPreTraverse(){ if(root == null) return; nonRecurPreTraverse(root); } private void nonRecurPreTraverse(BinaryNode root){ LinkedList<BinaryNode> stack = new LinkedList<MyBinaryTree.BinaryNode>(); BinaryNode currentNode; BinaryNode tmp; currentNode = root; while(currentNode != null || !stack.isEmpty()) { while(currentNode != null) { System.out.print(currentNode.ele + " ");//visit stack.push(currentNode); currentNode = currentNode.left; } if(!stack.isEmpty()) { tmp = stack.pop(); currentNode = tmp.right; } } } //先序递归遍历 public void preOrder(){ preorder(root); } private void preorder(BinaryNode root){ if(root == null) return;//base condition System.out.print(root.ele + " ");//visit preorder(root.left); preorder(root.right); } //后序非递归遍历 public void postNonRecurTraverse(){ if(root == null) return; postNonRecurTraverse(root); } private void postNonRecurTraverse(BinaryNode root){ LinkedList<BinaryNode> stack = new LinkedList<MyBinaryTree.BinaryNode>(); BinaryNode currentNode, tmp; currentNode = root; while(currentNode != null || !stack.isEmpty()) { while(currentNode != null) { stack.push(currentNode); currentNode = currentNode.left; } if(!stack.isEmpty()) { tmp = stack.getFirst(); //从左子树返回,需要判断它的右子树是否已经访问了 if(tmp.isFirst == false)//右子树还未被访问 { tmp.isFirst = true; currentNode = tmp.right; } else{//左右子树都已经访问了 tmp = stack.pop(); System.out.print(tmp.ele + " ");//visit // currentNode = null; } } }//while } public void postOrder(){ postOrder(root); } private void postOrder(BinaryNode root){ if(root == null) return; postOrder(root.left); postOrder(root.right); System.out.print(root.ele + " ");//visit } public static void main(String[] args) { MyBinaryTree mbt = new MyBinaryTree(); mbt.buildTree(); System.out.println("in order"); mbt.nonRecurInTraverse(); System.out.println(); mbt.inorder(); System.out.println("\npre order"); mbt.preOrder(); System.out.println(); mbt.nonRecurPreTraverse(); System.out.println("\n post order"); mbt.postOrder(); System.out.println(); mbt.postNonRecurTraverse(); } }
六,二叉查找树中序遍历应用
判断一棵树,是否是二叉查找树。采用中序遍历,如果遍历是有序的,则是一棵二叉查找树。代码如下:
import java.util.LinkedList; import java.util.List; public class Solution { public boolean isValidBST(TreeNode root) { LinkedList<TreeNode> stack = new LinkedList<>(); TreeNode currentNode,tmp; // int currentVal = Integer.MIN_VALUE; long currentVal = Long.MIN_VALUE; currentNode = root; while (currentNode != null || !stack.isEmpty()) { while (currentNode != null) { stack.push(currentNode); currentNode = currentNode.left; } if (!stack.isEmpty()) { tmp = stack.pop(); if (currentVal >= tmp.val) { return false; }else { currentVal = tmp.val; } currentNode = tmp.right; } } return true; } public List<Integer> inOrderNonRecurse(TreeNode root, List<Integer> nodes) { LinkedList<TreeNode> stack = new LinkedList<>(); TreeNode currentNode, tmp; currentNode = root; while (currentNode != null || !stack.isEmpty()) { while (currentNode != null) { stack.push(currentNode); currentNode = currentNode.left; } if (!stack.isEmpty()) { tmp = stack.pop(); nodes.add(tmp.val); currentNode = tmp.right; } } return nodes; } public List<Integer> inOrder(TreeNode root, List<Integer> nodes) { if (root == null) { return nodes; } inOrder(root.left, nodes); nodes.add(root.val); inOrder(root.right, nodes); return nodes; } public static class TreeNode{ int val; TreeNode left; TreeNode right; TreeNode(int x) { val = x; } } }
参考资料: