以前也寫過很多關於二叉樹的東西了,可是整體來講,二叉樹仍是一個很繞的東西,因此單獨擇出來寫一篇筆記,以前也沒計劃什麼的,就想到什麼寫什麼吧。不過該篇文章的主要內容是關於二叉樹的三種遍歷(前序、中序、後序)不一樣的實現方式(遞歸與非遞歸)。html
首先,我以爲頗有必要去完全理解一下遞歸。node
(1)遞歸的主體大概分兩部分:遞歸中止的條件、遞歸內容。spa
(2)遞歸應用的實例:這個超級多,就好比最典型的斐波那契數列。我的認爲,能夠用循環實現的,遞歸基本上均可以實現,但有時遞歸的效率不如循環。code
(3)遞歸又分爲單遞歸與多遞歸(二叉樹的三種遍歷遞歸方法均用到了雙遞歸!)htm
根據上面的三點,舉個例子先。blog
假設當x=0時,F(x)=1;不然F(x)=F(n-1)*n。這個時候就能夠用遞歸了,代碼實現以下。遞歸
class Solution{ public int F(int n) { //遞歸中止條件 if (n == 0) { return 1; } //遞歸內容 else { return F(n - 1) * n; } } }
代碼分析一下以下:get
二叉樹的三種遍歷:前序(根左右)、中序(左根右)、後序(左右根)it
首先看三種遍歷的遞歸實現方法。(特色:代碼清晰,量少,但不易理解)io
// (1)前序遍歷 public TreeNode PreOrder(TreeNode pRoot) { //遞歸終止條件 if (pRoot != null) { // 根->左->右 Console.Write(pRoot.data + " "); PreOrder(pRoot.left); PreOrder(pRoot.right); } } // (2)中序遍歷 public TreeNode MidOrder(TreeNode pRoot) { //遞歸終止條件 if (node != null) { // 左->根->右 MidOrder(pRoot.left); Console.Write(pRoot.data + " "); MidOrder(pRoot.right); } } // (3)後序遍歷 public TreeNode PostOrder(TreeNode pRoot) { //遞歸終止條件 if (pRoot != null) { // 左->右->根 PostOrder(pRoot.left); PostOrder(pRoot.right); Console.Write(pRoot.data + " "); } }
分析:
表達能力是多麼重要的事情啊,會是一回事,表述清楚又是一回事。難受啊,馬飛。。
固然,我寫的東西可能有點亂,可是想表現幾個想法:
遞歸和棧是密不可分的。
上述三個方法均存在一個打印,兩個遞歸,可是惟一的區別就是順序的不一樣,因此,如何理解呢!!!關鍵點:若是打印在遞歸後面,則遞歸是不受打印影響的,也就是,我遞歸要先執行完,纔開始執行你打印,可是若是打印在遞歸前面,至關於打印已經屬於這個遞歸體了,沒次遞歸的時候都要執行一次打印!!!
非遞歸下如何實現三種遍歷。
// 前序遍歷 public void PreOrderNoRecurise(Node<T> node) { if (node == null) { return; } // 定義一個棧存放數據 Stack<Node<T>> stack = new Stack<Node<T>>(); //把根節點放進去 stack.Push(node); //定義一個空節點名稱 Node<T> tempNode = null; while (stack.Count > 0) { // 根節點出棧打印 tempNode = stack.Pop(); Console.Write(tempNode.data); // 右子節點進棧 if (tempNode.right != null) { stack.Push(tempNode.right); } // 左子節點進棧 if (tempNode.left != null) { stack.Push(tempNode.left); } } } //中序遍歷 public void MidOrderNoRecurise(Node<T> node) { if (node == null) { return; } // 定義一個棧存放數據 Stack<Node<T>> stack = new Stack<Node<T>>(); //定義一個空節點 Node<T> tempNode = node; while (tempNode != null || stack.Count > 0) { // 左子節點進棧 while(tempNode != null) { stack.Push(tempNode); tempNode = tempNode.left; } // 2.出棧遍歷節點並打印 tempNode = stack.Pop(); Console.Write(tempNode.data); // 3.左子節點遍歷結束則跳轉到右子節點 tempNode = tempNode.right; } } //後序遍歷 public void PostOrderNoRecurise(Node<T> node) { if (root == null) { return; } // 兩個棧:一個存儲,一個輸出 Stack<Node<T>> stackIn = new Stack<Node<T>>(); Stack<Node<T>> stackOut = new Stack<Node<T>>(); Node<T> currentNode = null; // 根節點進棧 stackIn.Push(node); // 左->右->根 while (stackIn.Count > 0) { currentNode = stackIn.Pop(); stackOut.Push(currentNode); // 左子節點進棧 if (currentNode.left != null) { stackIn.Push(currentNode.left); } // 右子節點進棧 if (currentNode.right != null) { stackIn.Push(currentNode.right); } } while (stackOut.Count > 0) { // 依次遍歷各節點 Node<T> outNode = stackOut.Pop(); Console.Write(outNode.data); } }
非遞歸方法變化多樣,上述代碼借鑑Edison Zhou的文章,本身不想寫了,哈哈,可是道理並不難懂,不會向遞歸那樣難理解,有時間的時候再慢慢修改補充吧。。