Java二叉樹的構造與三種非遞歸遍歷算法

二叉樹的非遞歸遍歷可以依賴於棧結構解決。其中先序和中序遍歷思路較爲相似,後序遍歷需要另外設置一個訪問位變量,比前兩種較爲複雜一些。

首先是二叉樹的構造,這裏使用二叉樹的先序序列,遞歸的方法去構造,將構造二叉樹的任務分爲構造多個子樹的小任務。首先對樹根結點調用構造二叉樹的方法,在每一個節點處對左子樹和右子樹依次調用構造二叉樹的方法。

我們本篇使用下面的樹
在這裏插入圖片描述

這裏的先序序列是一個數組,在用循環結構去構造的時候,應該使索引 i 是一個全局變量,否則每一次遞歸地調用create()方法的時候,i 值都會是0,最後構造出來是錯誤的

public class BinaryTree<T> {

	public BinaryNode<T> root;
	public BinaryTree(T[] prelist){
		this.root = create(prelist);
	}
	private int i = 0;
	private BinaryNode<T> create(T[] prelist){
		BinaryNode<T> p = null;
		if(i < prelist.length) {
			T elem = prelist[i];
			i++;
			if(elem != null) {
				p = new BinaryNode<T> (elem);
				p.left = create(prelist);
				p.right = create(prelist);
			}
		}
		return p;
	}
}

這樣,構造出來了二叉樹的邏輯結構。
需要注意的是,構造的時候傳入的數列,應該是一個完全二叉樹的形式,也就是不存在的結點也要設爲null,否則這個方法錯誤
接下來是二叉樹的非遞歸先序遍歷方法
首先結合上面的構造體會一下效果

public static void main(String[] args) {
		//必須把所有葉子節點後面跟兩個null,否則輸出結果不對
		String[] prelist = {"A","B","D",null, "H",null,null,"E","I",null,null,
				"J",null,null,
				"C","F",null,"K",null,null,
				"G",null,null};
		BinaryTree<String> bitree = new BinaryTree<String>(prelist);
		bitree.preorderTraverse();
	}

輸出結果是:
先根次序遍歷 非遞歸
A B D ^ H ^ ^ E I ^ ^ J ^ ^ C F ^ K ^ ^ G ^

先序遍歷
先根次序遍歷,我們從根節點開始,設置一個指針變量指向當前節點,一直訪問左子樹(指針一直指向左子樹),在這個過程中遇到的每一個結點都要輸出它的元素值,然後入棧,直到左子樹爲空,我們將棧頂出棧,指針指向棧頂元素的右子樹,然後仍然是入棧然後指向左子樹(這裏就又遞歸了)。依次類推…

public void preorderTraverse() {
		System.out.println("先根次序遍歷 非遞歸");
		LinkedStack<BinaryNode<T>> stack = new LinkedStack<BinaryNode<T>>();
		BinaryNode<T> p = this.root;
		while (p != null || !stack.isEmpty()) {
			if (p != null) {
				System.out.print(p.data + " ");
				stack.push(p);
				p = p.left;
			}else {
				System.out.print("^ ");
				p = stack.pop();
				p = p.right;
			}
		}
		System.out.println();
	}

中序遍歷
然後是中序遍歷,他和先序的區別也就是。先序遍歷是在結點入棧的時候進行輸出元素值的。而中序遍歷是在元素出棧的時候輸出出棧元素的元素值。怎麼講也不如體會一下代碼!:

//@ author ZhaoKe
	public void inorderTraverse() {
		System.out.println("中根次序遍歷 非遞歸");
		LinkedStack<BinaryNode<T>> stack = new LinkedStack<BinaryNode<T>>();
		BinaryNode<T> p = this.root;
		while (p != null || !stack.isEmpty()) {
			if (p != null) {
				stack.push(p);
				p = p.left;
			}else {
				System.out.print("^ ");
				p = stack.pop();
				System.out.print(p.data + " ");
				p = p.right;
			}
		}
		System.out.println();
	}

它的輸出結果是:
中根次序遍歷 非遞歸
^ D ^ H ^ B ^ I ^ E ^ J ^ A ^ F ^ K ^ C ^ G

後序遍歷
後序遍歷不太一樣,這裏比較麻煩。由於順着二叉樹的左子樹去尋找最下面的右子樹的葉子結點,中間一定要有結點的入棧和出棧,那麼無論是入棧還是出棧去輸出結點的元素值,都是先序或中序遍歷的操作,後續自然不能使用了。
因此這裏我們設置一個臨時存儲的訪問變量 last,表示剛剛是哪一個結點已經被訪問過,而且剛剛訪問它的時候沒有輸出它的值。那麼這裏問題是何時去輸出它的值。
根據上面的例子,我們過程是:

  • 仍然一直順着左子樹去到達最下面的結點, 回到上一層結點不使用出棧操作,而是“返回棧頂元素”,

  • 然後去右子樹,如果右子樹不爲空並且剛剛未訪問過,就去右子樹。

  • 否則輸出結點的值,並把該節點出棧賦值給變量 last,表示它被訪問過。然後再回到上一層結點,

  • 該節點再次入棧,左子樹爲空,去右子樹,右子樹被訪問過,那麼這個結點輸出並出棧,又到了上一層結點。以此類推就完成了後序遍歷

//@ author ZhaoKe
	public void postorderTraverse() {
		System.out.println("後根次序遍歷 非遞歸");
		LinkedStack<BinaryNode<T>> stack = new LinkedStack<BinaryNode<T>>();
		BinaryNode<T> last = null;
		BinaryNode<T> p = this.root;
		while (p != null || !stack.isEmpty()) {
			if (p != null) {
				stack.push(p);
				p = p.left;
			}else {
				System.out.print("^ ");
				p = stack.peek();
				if(p.right != null && last != p.right) {
					p = p.right;
				}
				else {
					System.out.print(p.data+" ");
					last  =stack.pop();
//					p = stack.peek();//這是錯誤演示,這會導致死循環。會導致一直進入if裏面而不停地入棧出棧
					p = null;
				}	
			}//else
		}//while
		System.out.println();
	}

遍歷的結果是:
後根次序遍歷 非遞歸
^ ^ H ^ D ^ ^ I ^ ^ J ^ E ^ B ^ ^ ^ K ^ F ^ ^ G ^ C ^ A

–CUST

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章