二叉樹的非遞迴遍歷---JAVA實現
阿新 • • 發佈:2018-12-01
二叉樹的遞迴遍歷方式是很簡單的,當需要用非遞迴的方式遍歷時,就需要藉助棧這種資料結構,以前序遍歷為例,其定義為先訪問根節點,再以前序方式訪問左子樹,再以前序遍歷方式訪問右子樹,這是個遞迴的定義。對於前序遍歷,最先訪問的是根,然後是根左邊的孩子,在繼續訪問孩子的左邊孩子,走到底部,再從底部開始倒退回來,逐漸訪問右邊孩子,很自然的想到使用棧這種資料結構......(可以畫一個二叉樹看看其前序遍歷的過程就明白了),下面以程式碼說明:
二叉樹結構如圖:
/*二叉樹結構: A / \ B C / \ / E F I / \ \ G H J */
前序、中序、後序的遞迴、非遞迴訪問:
程式碼還包括遞迴建立二叉樹的部分,其中,後序遍歷有兩種非遞迴實現方法(對應使用了一個棧或兩個棧)
執行結果:public class BinTreeTraverse { /** * @ 二叉樹的非遞迴遍歷 */ public static void main(String[] args) { String str="ABE##FG##H##CI#J###"; TreeNode<Character> root=createBinTree(new StringBuilder(str)); System.out.print("********************preOrder******************\n"); preOrder(root); System.out.println(); preOrderLoop(root); System.out.print("\n********************inOrder******************\n"); inOrder(root); System.out.println(); inOrderLoop(root); System.out.print("\n*******************postOrder*******************\n"); postOrder(root); System.out.print("\n雙棧方式:"); postOrderLoop(root); System.out.print("\n單棧方式:"); postOrderLoop2(root); } //***************************前序******************************** public static void preOrder(TreeNode<Character> root){ if(root!=null){ System.out.print(root.data+" "); preOrder(root.leftChildren); preOrder(root.rightChildren); } } //PreOrder的非遞迴實現——需要藉助棧空間實現 public static void preOrderLoop(TreeNode<Character> root){ //根據遞迴的過程來想,它每遇到一個節點,就以之為根節點,不斷向左下放遍歷,入棧,直到空 //然後棧彈出,又以彈出點右孩子為根(非空的),繼續剛才左下滑的過程 MyStack<TreeNode<Character>> stack=new MyStack<TreeNode<Character>>(); TreeNode<Character> temp=root; while(temp!=null || !stack.isEmpty()){ while(temp!=null){ visit(temp); stack.push(temp); temp=temp.leftChildren; } if(!stack.isEmpty()){ temp=stack.pop(); temp=temp.rightChildren; } } } //*****************************中序********************************** public static void inOrder(TreeNode<Character> root){ if(root!=null){ inOrder(root.leftChildren); visit(root); inOrder(root.rightChildren); } } public static void inOrderLoop(TreeNode<Character> root){ TreeNode<Character> temp=root; MyStack<TreeNode<Character>> stack =new MyStack<TreeNode<Character>>(); while(temp!=null || !stack.isEmpty()){ while(temp!=null){ stack.push(temp); temp=temp.leftChildren; } if(!stack.isEmpty()){ temp=stack.pop(); visit(temp); temp=temp.rightChildren; } } } //********************************後序********************************** public static void postOrder(TreeNode<Character> root){ if(root!=null){ postOrder(root.leftChildren); postOrder(root.rightChildren); visit(root); } } public static void postOrderLoop(TreeNode<Character> root){ /*(可結合後序遍歷結果進行分析) * 與前序遍歷相反,後序遍歷相當於每次從根節點開始,一直向右下方向搜尋直到空,逐個入棧,遇到null後, * 依次出棧,讓出棧點的左孩子執行相同的操作(需要一個輔助棧儲存);最後將棧的元素一起彈出 * 因此:後序遍歷需要用兩個棧 * */ TreeNode<Character> temp=root; MyStack<TreeNode<Character>> stack=new MyStack<TreeNode<Character>>(); MyStack<TreeNode<Character>> stackHelp=new MyStack<TreeNode<Character>>(); while(temp!=null || !stack.isEmpty()){ while(temp!=null){ stack.push(temp); stackHelp.push(temp);//此句是唯一與前序遍歷不同的地方(進入輔助棧) temp=temp.rightChildren; } if(!stack.isEmpty()){ temp=stack.pop(); temp=temp.leftChildren; } } while(!stackHelp.isEmpty()){ temp=stackHelp.pop(); visit(temp); } } /*使用單個棧也可實現非遞迴的後序遍歷 * 需要有一個節點記錄上次訪問過的節點 * */ public static void postOrderLoop2(TreeNode<Character> root){ TreeNode<Character> temp = root; TreeNode<Character> preNode =null; MyStack<TreeNode<Character>> stack =new MyStack<TreeNode<Character>>(); while(temp!=null || !stack.isEmpty()){ //同前序一樣,左邊所有節點入棧 while(temp!=null){ stack.push(temp); temp=temp.leftChildren; } if(!stack.isEmpty()){ temp=stack.peek(); if(temp.rightChildren!=null && temp.rightChildren!=preNode){ temp=temp.rightChildren; }else{//說明右子樹為空或已經訪問過 temp=stack.pop(); visit(temp); preNode=temp; temp=null; //防止剛出棧的節點的左孩子繼續入棧(即跳過上面的while迴圈) } } } } //******************************************************************** private static void visit(TreeNode<Character> root){ System.out.print(root.data+" "); } //遞迴的建立二叉樹 public static TreeNode<Character> createBinTree(StringBuilder sb){ //只能傳StringBuilder,不能傳String,因為第二次遞迴必須使用第一次遞迴使用完之後的資料 //string雖然也傳遞的是字串的引用,但遞迴過程中並不能改變其值substring()返回的是新字串 char data=sb.charAt(0); sb.deleteCharAt(0); TreeNode<Character> rootNode=null; if(data!='#'){ rootNode=new TreeNode<Character>(data); rootNode.leftChildren=createBinTree(sb); rootNode.rightChildren=createBinTree(sb); } return rootNode; } } class TreeNode<E>{ E data; TreeNode<E> leftChildren; TreeNode<E> rightChildren; TreeNode(E data){ this.data=data; leftChildren=null; rightChildren=null; } }
********************preOrder******************
A B E F G H C I J
A B E F G H C I J
********************inOrder******************
E B G F H A I J C
E B G F H A I J C
*******************postOrder*******************
E G H F B J I C A
雙棧方式:E G H F B J I C A
單棧方式:E G H F B J I C A
可以看出,非遞迴的方式均使用了棧這種資料結構,這也是棧在程式設計中最常見的用途之一。