使用數組自上而下,自左至右存儲完全二叉樹上的結點元素,即將完全二叉樹上編號為i的結點元素存儲在某個數組下標為i-1的分量中,然後通過一些方法確定結點在邏輯上的父子和兄弟關系。
根據二叉樹的性質,完全二叉樹和滿二叉樹樹采用順序存儲比較合適,樹中結點的序號可以唯一地反映出結點之間的邏輯關系,既能節省存儲空間,又能利用數組元素下標值確定結點在二叉樹中的位置,以及結點之間的關系。
而對於一般的二叉樹也必須按照完全二叉樹的形式存儲,也就是必須添加一些並不存在的虛擬結點,造成空間的浪費。
圖1 二叉樹順序存儲
順序存儲的關鍵是數組下標確定結點的位置,如結點從1開始編號,那麼結點i的左孩子為2*i,右孩子為2*i+1。不存在結點用0表示。
先回憶一下二叉樹的一些性質:
(1) 非空二叉樹第k層最多有2k-1個結點
(2) 高度為h的二叉樹至多有2h-1個結點
首先按照樹的高度height初始化一顆樹,以及一些有用的方法,代碼如下:
public class ArrayBiTree<T> { private Object[] data; private int height = 3; // 樹的高度 默認為3 private int n; // 結點個數 public ArrayBiTree() { data = new Object[(int) Math.pow(2, height)]; init(); } /** * 指定深度初始化一個樹 * @param height 樹的深度 */ public ArrayBiTree(int height) { this.height = height; data = new Object[(int) Math.pow(2, height) - 1]; } private void init(){ System.out.println("默認生成一顆完全二叉樹,高度為3:"); for(int i=0; i<(int) Math.pow(2, height) - 1; i++){ data[i] = i+1; n++; } print(); } /** * 判斷結點是否存在 * @param index 根節點從 1 開始 * @return */ public boolean isExist(int index){ if(index > n) return false; return Integer.valueOf(data[index-1].toString()) != 0; } }
/** * 層次遍歷,利用隊列是實現 */ public void levelOrder(){ RingBuffer<Integer> queue = new RingBuffer<Integer>(n+1); queue.put(1); // 根節點先進隊列 while(queue.size()>0){ int tmp = queue.get(); System.out.print(data[tmp-1]+" "); if (isExist(2 * tmp)) { // 如果左子樹存在,把左子樹編號入棧 queue.put(2 * tmp); } if (isExist(2 * tmp + 1)) { // 如果右子樹存在,把右子樹編號入棧, queue.put(2 * tmp + 1); } } }
/** * 先序遍歷,遞歸實現Recursion * @param index 根節點從 1 開始 */ public void preOrderRecur(int index){ if(isExist(index)){ //判斷結點是否存在 System.out.print(data[index-1]+" "); // 訪問根節點 preOrderRecur(2*index); // 遞歸遍歷左子樹 preOrderRecur(2*index + 1); // 遞歸遍歷右子樹 } }
實現方法1:
/** * 先序遍歷,非遞歸實現,借助棧來實現<p> * 根節點先入棧,訪問棧頂結點,若棧頂元素的右孩子存在則入棧,若棧頂元素的左孩子存在則入棧,如此循環直到棧空 */ public void preOrder(){ ArrayStack<Integer> stack = new ArrayStack<Integer>(n); stack.push(1); // 根節點入棧 while(!stack.isEmpty()){ int tmp = stack.pop(); // 取根結點,把每個結點都看作根節點 System.out.print(data[tmp-1]+" "); // 訪問根結點 if (isExist(2 * tmp + 1)) { // 如果根節點的右子樹存在,把右子樹編號入棧 stack.push(2 * tmp + 1); } if (isExist(2 * tmp)) { // 如果根節點的左子樹存在,把左子樹編號入棧 stack.push(2 * tmp); } } }
實現方法2:
/** * 先序遍歷1,非遞歸實現,借助棧來實現<p> * @param index 根節點從 1 開始 */ public void preOrderOne(int index){ ArrayStack<Integer> stack = new ArrayStack<Integer>(n); while (isExist(index) || !stack.isEmpty()) { // (1) 首先訪問根節點,一直往左下方走,直到一個左孩子不存在的結點。 while (isExist(index)) { System.out.print(data[index - 1] + " "); stack.push(index); // 根節點入棧,把每個結點都看作一個根節點,檢查其左右孩子是否存在 index = 2 * index; } // 此時,棧內是從根節點左孩子開始的左孩子���最後一個結點是不存在左孩子的結點 // (2) 拿棧頂元素,看其右孩子是否存在,把當前結點置為其右孩子,繼續循環判斷(1) if (!stack.isEmpty()) { int tmp = stack.pop(); // 彈出的左子樹結點 index = 2 * tmp + 1; // 看它的右孩子是否存在 } } }
/** * 中序遍歷,遞歸實現Recursion * @param index 根節點從 1 開始 */ public void inOrderRecur(int index){ if(isExist(index)){ inOrderRecur(2*index); // 遞歸遍歷左子樹 System.out.print(data[index-1]+" "); // 訪問根節點 inOrderRecur(2*index + 1); // 遞歸遍歷右子樹 } }
/** * 中序遍歷,非遞歸實現,更改訪問時機即可 * @param index */ public void inOrder(int index){ ArrayStack<Integer> stack = new ArrayStack<Integer>(n); while(isExist(index) || !stack.isEmpty()){ while(isExist(index)){ stack.push(index); // 根節點入棧 index = 2 * index; // 是否存在左孩子 } if(!stack.isEmpty()){ int tmp = stack.pop(); // 彈出左孩子 System.out.print(data[tmp-1]+" "); // 訪問結點 index = 2 * tmp + 1; // 看左孩子的右孩子是否存在 } } }
/** * 後序遍歷,遞歸實現Recursion * @param index 根節點從 1 開始 */ public void postOrderRecur(int index){ if(isExist(index)){ postOrderRecur(2*index); // 遞歸遍歷左子樹 postOrderRecur(2*index + 1); // 遞歸遍歷右子樹 System.out.print(data[index-1]+" "); // 訪問根節點 } }
/** * 後序遍歷,非遞歸實現<p> * 與前中序相比實現比較麻煩,先訪問左子樹再訪問右子樹 */ public void postOrder(int index){ ArrayStack<Integer> stack = new ArrayStack<Integer>(n); int visited = 0; // 標記前一個已被訪問的結點 while(isExist(index) || !stack.isEmpty()){ while(isExist(index)){ stack.push(index); // 根節點入棧 index = 2 * index; }// 先把 index 的左孩子全部找到 int top = stack.peek(); // 查看棧頂元素,沒有彈出,訪問完右孩子之後在彈出訪問根節點 // 如果當前結點不存在右孩子或者右孩子已經訪問過,則訪問當前結點 if(!isExist(2*top+1) || (2*top+1) == visited){ int tmp = stack.pop(); System.out.print(data[tmp-1]+" "); visited = tmp; } else { // 否則訪問右孩子 index = 2 * top + 1; } } }
public static void main(String[] args) { ArrayBiTree<Integer> biTree = new ArrayBiTree<Integer>(); System.out.print("先序遍歷(遞歸):"); biTree.preOrderRecur(1); System.out.print("\n中序遍歷(遞歸):"); biTree.inOrderRecur(1); System.out.print("\n後序遍歷(遞歸):"); biTree.postOrderRecur(1); System.out.print("\n層次遍歷:"); biTree.levelOrder(); System.out.print("\n先序遍歷(非遞歸):"); biTree.preOrder(); // biTree.preOrderOne(1); System.out.print("\n中序遍歷(非遞歸):"); biTree.inOrder(1); System.out.print("\n後序遍歷(非遞歸):"); biTree.postOrder(1); System.out.println(); biTree.stdIn(); }
相關閱讀:
二叉樹的常見問題及其解決程序 http://www.linuxidc.com/Linux/2013-04/83661.htm
【遞歸】二叉樹的先序建立及遍歷 http://www.linuxidc.com/Linux/2012-12/75608.htm
在JAVA中實現的二叉樹結構 http://www.linuxidc.com/Linux/2008-12/17690.htm
【非遞歸】二叉樹的建立及遍歷 http://www.linuxidc.com/Linux/2012-12/75607.htm
二叉樹遞歸實現與二重指針 http://www.linuxidc.com/Linux/2013-07/87373.htm
二叉樹先序中序非遞歸算法 http://www.linuxidc.com/Linux/2014-06/102935.htm
輕松搞定面試中的二叉樹題目 http://www.linuxidc.com/linux/2014-07/104857.htm