轉載自原文
一、易懂的形象理解
其實從名字就可以很好的理解這三種遍歷,我在第二點時候說,但是估計能翻到我的文的同學們之前肯定看過好多類似的了,那咱們換個思路~ 先用我想的一種簡單易懂的形象思維理解一下前序、中序、後序 +層序!
1、先序遍歷
先序遍歷可以想象成,小人從樹根開始繞着整棵樹的外圍轉一圈,經過結點的順序就是先序遍歷的順序
先序遍歷結果:ABDHIEJCFKG
讓我們來看下動畫,和小人兒一起跑兩遍就記住啦,記住是繞着外圍跑哦
2、中序遍歷
中序遍歷可以想象成,按樹畫好的左右位置投影下來就可以了
中序遍歷結果:HDIBEJAFKCG
下面看下投影的過程動畫,其實就是按左右順序寫下來就行了
3、後序遍歷
後序遍歷就像是剪葡萄,我們要把一串葡萄剪成一顆一顆的。
還記得我們先序遍歷繞圈的路線麼?
就是圍着樹的外圍繞一圈,如果發現一剪刀就能剪下的葡萄(必須是一顆葡萄),就把它剪下來,組成的就是後序遍歷了。
後序遍歷結果:HIDJEBKFGCA
讓我們來看下動畫
4、層序遍歷
層序遍歷太簡單了,就是按照一層一層的順序,從左到右寫下來就行了。
後序遍歷結果:ABCDEFGHIJK
不知道通過這種方式,有沒有覺得閉着眼睛都能寫出前序、中序、後序 、層序了呀,不過這只是爲了大家好理解,我想出的一種形象思維,爲了用代碼實現,我們還需要具體瞭解一下前序、中序、後序遍歷。
二、真正理解三種遍歷
來,讓我們先把所有空結點都補上。
還記得我們先序和後序遍歷時候跑的順序麼?按照這個順序再跑一次,就是圍着樹的外圍跑一整圈。
讓我們來理解一下繞着外圍跑一整圈的真正含義是:遍歷所有結點時,都先往左孩子走,再往右孩子走。
觀察一下,你有什麼發現?
有沒有發現,除了根結點和空結點,其他所有結點都有三個箭頭指向它。
一個是從它的父節點指向它,一個是從它的左孩子指向它,一個是從它的右孩子指向它。
一個結點有三個箭頭指向它,說明每個結點都被經過了三遍。一遍是從它的父節點來的時候,一遍是從它的左孩子返回時,一遍是從它的右孩子返回時。
其實我們在用遞歸算法實現二叉樹的遍歷的時候,不管是先序中序還是後序,程序都是按照上面那個順序跑遍所有結點的。
先序中序和後序唯一的不同就是,在經過結點的三次中,哪次訪問(輸出或者打印或者做其他操作)了這個結點。有點像大禹治水三過家門,他會選擇一次進去。
先序遍歷顧名思義,就是在第一次經過這個結點的時候訪問了它。就是從父節點來的這個箭頭的時候,訪問了它。
中序遍歷也和名字一樣,就是在第二次經過這個結點的時候訪問了它。就是從左孩子返回的這個箭頭的時候,訪問了它。
後序遍歷,就是在第三次經過這個結點的時候訪問了它。就是從右孩子返回的這個箭頭的時候,訪問了它。
怎麼樣,這樣有沒有很好的理解?其實不管是前序中序還是後序,在程序裏跑的時候都是按照同樣的順序跑的,每個結點經過三遍,第幾遍訪問這個結點了,就叫什麼序遍歷。
當我們腦子裏有這個概念的時候, 再去看實現代碼就很好理解了,下一篇博文我會貼出和講解具體的實現代碼。
三、代碼實現
需求:把一個數組生成一個二叉樹,並打印前序、中序、後序的結果。
int[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
這個數組生成的二叉樹如下:
創建個java文件叫:TreeNode.java,內容如下,可直接複製使用
public class TreeNode {
private int[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9};
private static List<Node> nodeList = null;
//內部類 結點
private static class Node {
Node leftChild;
Node rightChild;
int data;
//構造方法初始化
Node(int newData) {
leftChild = null;
rightChild = null;
data = newData;
}
}
public void createBinTree() {
if (array.length > 0) {
nodeList = new LinkedList<Node>();
// 將一個數組的值依次轉換爲Node節點
for (int nodeIndex = 0; nodeIndex < array.length; nodeIndex++) {
nodeList.add(new Node(array[nodeIndex]));
}
// 對前lastParentIndex-1個父節點按照父節點與孩子節點的數學關係建立二叉樹
for (int parentIndex = 0; parentIndex < array.length / 2 - 1; parentIndex++) {
// 左孩子
nodeList.get(parentIndex).leftChild = nodeList.get(parentIndex * 2 + 1);
// 右孩子
nodeList.get(parentIndex).rightChild = nodeList.get(parentIndex * 2 + 2);
}
// 最後一個父節點:因爲最後一個父節點可能沒有右孩子,所以單獨拿出來處理
int lastParentIndex = array.length / 2 - 1;
// 最後一個節點的左孩子
if (array.length != 1) {
nodeList.get(lastParentIndex).leftChild = nodeList.get(lastParentIndex * 2 + 1);
}
// 最後一個節點的右孩子,如果數組的長度爲奇數並且不止一個節點才建立右孩子
if (array.length != 1 && array.length % 2 == 1) {
nodeList.get(lastParentIndex).rightChild = nodeList.get(lastParentIndex * 2 + 2);
}
}
}
/**
* 先序遍歷
*/
public static void preOrderTraverse(Node node) {
if (node == null)
return;
System.out.print(node.data + " ");
preOrderTraverse(node.leftChild);
preOrderTraverse(node.rightChild);
}
/**
* 中序遍歷
*/
public static void inOrderTraverse(Node node) {
if (node == null)
return;
inOrderTraverse(node.leftChild);
System.out.print(node.data + " ");
inOrderTraverse(node.rightChild);
}
/**
* 後序遍歷
*/
public static void postOrderTraverse(Node node) {
if (node == null)
return;
postOrderTraverse(node.leftChild);
postOrderTraverse(node.rightChild);
System.out.print(node.data + " ");
}
public static void main(String[] args) {
TreeNode binTree = new TreeNode();
binTree.createBinTree();
// nodeList中第0個索引處的值即爲根節點
Node root = nodeList.get(0);
System.out.println("先序遍歷:");
preOrderTraverse(root);
System.out.println();
System.out.println("中序遍歷:");
inOrderTraverse(root);
System.out.println();
System.out.println("後序遍歷:");
postOrderTraverse(root);
}
}
運行結果:
先序遍歷:
1 2 4 8 9 5 3 6 7
中序遍歷:
8 4 9 2 5 1 6 3 7
後序遍歷:
8 9 4 5 2 6 7 3 1