1. 程式人生 > >二叉樹的非遞迴遍歷---JAVA實現

二叉樹的非遞迴遍歷---JAVA實現

        二叉樹的遞迴遍歷方式是很簡單的,當需要用非遞迴的方式遍歷時,就需要藉助棧這種資料結構,以前序遍歷為例,其定義為先訪問根節點,再以前序方式訪問左子樹,再以前序遍歷方式訪問右子樹,這是個遞迴的定義。對於前序遍歷,最先訪問的是根,然後是根左邊的孩子,在繼續訪問孩子的左邊孩子,走到底部,再從底部開始倒退回來,逐漸訪問右邊孩子,很自然的想到使用棧這種資料結構......(可以畫一個二叉樹看看其前序遍歷的過程就明白了),下面以程式碼說明:

二叉樹結構如圖:

/*二叉樹結構:
       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 

        可以看出,非遞迴的方式均使用了棧這種資料結構,這也是棧在程式設計中最常見的用途之一。