二叉樹是每一個節點最多有兩個子樹的樹結構,度多是0,1,2;java
完成二叉樹:從左到右依次填滿;
滿二叉樹:除了葉子節點,全部節點都有兩個孩子,而且全部葉子節點在同一層;node
1.徹底二叉樹除了最後一層外,下一層節點個數是上一層兩倍,
若是一顆徹底二叉樹的節點總數是n,那麼葉子節點個數爲n/2(n爲偶數)或(n+1)/2(n爲奇數);git
寫遞歸算法的關鍵就是明確函數的定義是什麼,而後相信這個定義,利用這個定義推道出最終結果,毫不要跳入遞歸的細節 (人的小腦殼才能堆幾層棧)算法
寫樹相關的算法,簡單的就是說,就是搞清楚當前root節點「該作什麼,何時作」這兩點,而後根據函數定義遞歸調用子節點。框架
把題目的要求細化,搞清楚根節點應該作什麼,而後剩下的事情拋給前/中/後序的遍歷框架就好了,難點在於如何經過題目的要求思考出每個節點須要作什麼。函數
遞歸的時候不要太在乎實現的細節,其本質上是經過棧來實現的,每次在方法裏本身調本身,就把新調用的本身壓棧,是一個有去有回的過程。
對於遞歸,關鍵就是要清楚函數的功能,何時停下來。
對於前序遍歷,想先打印根節點,再左再右;那就先輸出,再去遞歸調用,傳入當前節點的左子樹,左子樹一樣打印它的根,傳入根的左子樹,知道整個左子樹處理完了,再去處理右子樹;post
class BinaryTreeTraverse<T>{ /** * 前序遍歷(遞歸實現); */ public static void preOrderByRecursion(TreeNode node){ if (node == null) return; //遞歸終止條件; System.out.println(node.value); //獲取根節點的值; preOrderByRecursion(node.left); //左子樹的根節點; preOrderByRecursion(node.right);//右子樹的根節點; } /** * 中序遍歷(遞歸實現) */ public static void inOrderByRecursion(TreeNode node){ if (node == null) return; //遞歸終止; inOrderByRecursion(node.left); //先左子樹; System.out.println(node.value); //再根節點; inOrderByRecursion(node.right); //再右節點; } /** * 後序遍歷(遞歸實現) */ public static void postOrderByRecursion(TreeNode node){ if (node == null) return; postOrderByRecursion(node.left); postOrderByRecursion(node.right); System.out.println(node.value); }
時間複雜度:0(N),每一個節點遍歷N次;
空間複雜度:O(N),遞歸過程當中棧的開銷;ui
前序遍歷
前序遍歷就是咱們來手動實如今遞歸過程當中的棧。
想要實現先左再右,那壓棧的時候右先入棧,左再入棧。
以下圖所示;
規則:
壓入根節點;
1.彈出就打印;
2.若有右孩子,壓入右;
3.若有左孩子,壓入左;
重複1.2.3
思考一下這個過程;其實就是至關於兩個孩子來替換棧裏的根節點,這就很符合前序的定義,根先走,左子樹幹到了最頂部,要是我左子樹還有左孩子,那我也走,兩個孩子來替我,每次都是我先走,我左孩子這邊整個都完事了,再來我右孩子,由於我右孩子被壓在最下面。this
public static void preOrder(TreeNode node){ Stack<TreeNode> stack = new Stack<>(); if (node != null){ stack.push(node); //根節點入棧; } while (!stack.isEmpty()){ TreeNode top = stack.pop(); //彈出就打印; System.out.println(top.value); if (top.right != null) stack.push(top.right); //依次入棧右節點和左節點; if (top.left != null) stack.push(top.left); } }
中序遍歷
規則:
1.整條左邊界依次入棧;
2.條件1執行不了了,彈出就打印;
3.來到彈出節點的右子樹上,繼續執行條件1;(右樹爲空,執行2.彈出就打印;右樹有節點,執行1.壓棧;
說明:將整個樹全用左邊界去看,都是先處理了左邊界,將左邊界分解成了左頭,頭先入,左再入,而後弄不動了,彈出,而後看其右節點,再把右節點裏的左依次進去;就這樣往返;
以下如所示;.net
public static void inOrder(TreeNode node){ Stack<TreeNode> stack = new Stack<>(); while (!stack.isEmpty() || node != null){ if (node != null){ //條件1;能往左走就往左走; stack.push(node); node = node.left; }else { TreeNode top = stack.pop(); //條件2;彈出打印; System.out.println(top.value); node = top.right; //條件3;來彈出節點右樹上,繼續1; } } }
後序遍歷
後序遍歷能夠用前序遍從來解決,想一下前序遍歷:根左右,咱們先壓右樹再壓左樹。怎麼實現根右左呢,能夠先壓左樹再壓右樹嘛,而後反過來不就是左右根了嗎?(反過來用棧來實現,棧一個很大的做用就是實現逆序)
public static void postOrder(TreeNode node){ Stack<TreeNode> stackA = new Stack<>(); Stack<TreeNode> stackB = new Stack<>(); if (node != null){ stackA.push(node); } while (!stackA.isEmpty()){ TreeNode top = stackA.pop(); stackB.push(top); //棧A彈出的進入棧B;先實現根右左,B倒序實現左右根; if (top.left != null) stackA.push(top.left); if (top.right != null) stackA.push(top.right); } while (!stackB.isEmpty()){ System.out.println(stackB.pop().value); } }
二叉樹的結構中只有父節點指向孩子節點,孩子節點不能向上指,因此須要棧。
而Morris遍歷的實質就是讓下層節點也能指向上層,怎麼辦呢,一個節點有兩個指針,左和右,若是這兩個指針都有指向具體節點了那指定用不上了。
可是二叉樹但是有不少空閒指針啊,好比說全部的葉子節點,它們的指針就都指向null,因此能夠利用其right指針指向上層。
這樣把下層往上層創建鏈接之後,cur指針就能夠完整的順着一個鏈條遍歷完整個樹。
由於不用堆棧,因此其空間複雜度變爲O(1);
以下圖所示:
cur指針走的順序:1 2 4 2 5 1 3 6 3 7;
核心: 以某個根節點開始,找到其左子樹的最右節點(必然是個葉子節點),而後利用其right指針指向根節點(創建從下到上的鏈接)
原則:
(迭代法的中序遍歷咱們能夠將整個樹所有分紅左邊界去看,如上面的圖,其實在morris遍歷裏咱們能夠將整個樹所有分紅右邊界來看)
public static void morris(TreeNode node){ if(node == null){ return; } TreeNode cur = node; TreeNode mostRightNode = null; //記錄cur左子樹的最右節點; while(cur != null){ mostRightNode = cur.left; if(mostRightNode != null){ //cur有左子樹,就證實有下一層; //找到cur左子樹的最右節點(找到cur下一層右邊界的最後一個) while(mostRight.right != null && mostRightNode != cur){ //1.最右節點爲空說明到頭了,找到了; //2.最右節點指向上層說明已經處理過了,來過了; mostRightNode = mostRightNode.right; } //走到這裏證實跳出上面循環,無非兩個緣由: //1.右邊沒了;2.右邊指向上層了(以前就處理過了); if(mostRightNode.right == null){ mostRightNode.right = cur; //創建從最右節點到cur的鏈接; cur = cur.left; //處理下一個節點; continue; }else{ //能到這裏說明已經創建了最右節點到cur的鏈接; //也說明cur指的這個節點是第二次到了,斷開鏈接; mostRightNode.right = null; } } //cur右移的狀況: //1.cur沒有左子樹了(天然要開始處理右子樹) //2.cur有左子樹,可是cur左子樹最右節點已經指向cur了(執行完上面else斷開後,cur左邊已經徹底處理好了,開始右移。) cur = cur.right; } }
前序遍歷
1.對於cur只達到一次的節點(沒有左子樹),cur達到就打印;
2.對於cur到達兩次的節點(有左子樹),到達第一次時打印;
public static void preOrderMorris(TreeNode node){ if (node == null){ return; } TreeNode cur = node; TreeNode mostRightNode = null; while (cur != null){ mostRightNode = cur.left; if (mostRightNode != null){ //到達兩次的節點; //找到cur左子樹的最右節點; while (mostRightNode.right != null && mostRightNode.right != cur){ mostRightNode = mostRightNode.right; } if (mostRightNode.right == null){ mostRightNode.right = cur; //指向上層cur; System.out.println(cur.value); //第一次到的時候打印; cur = cur.left; continue; }else{ mostRightNode.right = null; //第二次到時不打印; } }else { System.out.println(cur.value); //只到達一次的節點; } cur = cur.right; } }
中序遍歷
1.對於cur只達到一次的節點(沒有左子樹),cur達到就打印;
2.對於cur到達兩次的節點(有左子樹),到達第二次時打印;
public static void inOrderMorris(TreeNode node){ if (node == null){ return; } TreeNode cur = node; TreeNode mostRightNode = null; while (cur != null){ mostRightNode = cur.left; if (mostRightNode != null){ //有左子樹,到達兩次的節點; while (mostRightNode.right != null && mostRightNode.right != cur){ mostRightNode = mostRightNode.right; } if (mostRightNode == null){ mostRightNode.right = cur; //第一次不打印; cur = cur.left; continue; }else { System.out.println(cur.value); //第二次遇到時打印; mostRightNode.right = null; } }else { System.out.println(cur.value); //只到達一次的節點,遇到就打印; } cur = cur.right; } }
後序遍歷
後序遍歷比前面兩個要複雜一點;
將一個節點的連續右節點當成是一個單鏈表看,以下圖所示:
當咱們到達最左側,也就是左邊連線已經建立完畢了。
打印 4
打印 5 2
打印 6
打印 7 3 1
咱們將一個節點的連續右節點當成一個單鏈表來看待。
當咱們返回上層以後,也就是將連線斷開的時候,打印下層的單鏈表。
好比返回到 2,此時打印 4
好比返回到 1,此時打印 5 2
好比返回到 3,此時打印 6
最後別忘記頭節點那一串,即1 3 7
那麼咱們只須要將這個單鏈表逆序打印就好了。
這裏不該該打印當前層,而是下一層,不然根結點會先與右邊打印。
public static void postOrderMorris(TreeNode node){ if (node == null){ return; } TreeNode cur = node; TreeNode mostRightNode = null; while (node != null){ mostRightNode = cur.left; if (mostRightNode != null){ while (mostRightNode.right != null && mostRightNode.right != cur){ mostRightNode = mostRightNode.right; } if (mostRightNode.right == null){ mostRightNode.right = cur; cur = cur.left; continue; }else { //能到這裏的都是達到過兩次的,也就是是有左孩子的。 mostRightNode.right = null; //這時候是已經返回上層以後,斷開了鏈接,因此打印下層的單鏈表; postMorrisPrint(cur.left); } } cur = cur.right; } postMorrisPrint(node); //最後把頭節點那一串右打印一遍; } public static void postMorrisPrint(TreeNode node){ TreeNode reverseList = postMorrisReverseList(node); //反轉鏈表; TreeNode cur = reverseList; while (cur != null){ System.out.println(cur.value); cur = cur.right; } postMorrisReverseList(reverseList); //最後再還原; } public static TreeNode postMorrisReverseList(TreeNode node){ TreeNode cur = node; TreeNode pre = null; while (cur != null){ TreeNode next = cur.right; cur.right = pre; pre = cur; cur = next; } return pre; }
層次遍歷顧名思義就是一層一層的遍歷。從上到下,從左到右,那須要藉助什麼結構呢?
能夠採用隊列的結構,利用其先進先出的特性,每一層依次入隊,再依次出隊。
對該層節點進行出隊時,將這個節點的左右節點入隊,這樣當一層全部節點出隊完成後,下一層也入隊完成了。
/** * 層次遍歷 * 藉助隊列的結構,每一層依次入隊,再依次出隊; * 對該層節點進行出隊操做時,須要將該節點的左孩子和右孩子入隊; */ public static int layerOrder(TreeNode node){ if (node == null) return 0; Queue<TreeNode> queue = new LinkedList<>(); queue.add(node); while (!queue.isEmpty()){ int size = queue.size(); //當前層的節點數量; for (int i = 0; i < size; i++){ TreeNode front = queue.poll(); System.out.println(front.value); if (front.left != null) queue.add(front.left); if (front.right != null) queue.add(front.right); } } }
二叉樹的最大深度是根節點到最遠葉子結點的距離;
遞歸實現
1.終止條件:在二叉樹爲空的時候,深度爲1;
2.縮小範圍,等價關係:給定一個二叉樹,其深度爲左子樹的深度和右子樹的深度的最大值+1;
/** * 求最大深度(遞歸) * 最大深度是左子樹和右子樹的最大深度的大的那個+1; */ public static int maxDepthByRecursion(TreeNode node){ if(node == null) return 0; int leftDepth = maxDepthByRecursion(node.left); int rightDepth = maxDepthByRecursion(node.right); return Math.max(leftDepth,rightDepth)+1; }
非遞歸實現(層次遍歷)
關鍵點:每遍歷一層,則計數器加+1;直到遍歷完成,獲得樹的深度。
採用二叉樹的層次遍歷,來計數總共有多少層,採用隊列的結構,當前層節點出隊,計數器加1,而後把下一層的節點所有入隊,直到隊爲空。
/** * 求最大深度(非遞歸) * 層次遍歷(BFS) * 每遍歷一層,則計數器加+1;直到遍歷完成,獲得樹的深度。 */ public static int maxDepth(TreeNode node){ if (node == null) return 0; Queue<TreeNode> queue = new LinkedList<>(); int level = 0; //層數; queue.add(node); while (!queue.isEmpty()){ level++; int levelNum = queue.size(); //每層的節點數; for (int i = 0; i < levelNum; i++){ TreeNode front = queue.poll(); //當前層出隊,下一層入隊; if (front.left != null) queue.add(front.left); if (front.right != null) queue.add(front.right); } } return level; }
二叉樹的深度是根節點到最近葉子節點的距離;
遞歸實現
此題不能像最大深度那樣直接求兩顆子樹的最大而後+1,最大深度能夠是由於取大值不會影響一棵樹爲空的時候。可是取最小就不同了,若是一棵樹爲空,那最小的應該是不爲空的那邊的值,可是還按原來方式就變成了0+1;好比下面這個例子:最小深度應該我2.可是按原來方式寫的話最小深度就會變爲1.因此,在處理每個節點的時候,若是有兩個孩子,那就能夠繼續取小+1,若是隻有一個孩子,那就只能去遞歸它的孩子。
/** * 求最小深度(遞歸) * 注意和求最大深度的區別; */ public static int minDepthByRecursion(TreeNode node){ if (node == null) return 0; if (node.right == null && node.left == null) return 1; if (node.left == null && node.right != null) return minDepthByRecursion(node.right) + 1; if (node.right == null && node.left != null) return minDepthByRecursion(node.left) + 1; return Math.min(minDepthByRecursion(node.left), minDepthByRecursion(node.right))+1; }
非遞歸實現(層次遍歷)
關鍵點:每遍歷一層,則計數器加+1;在遍歷的過程當中,若是出現了沒有葉子節點的節點,那就能夠結束了,就是最小深度。
採用二叉樹的層次遍歷,來計數總共有多少層,採用隊列的結構,當前層節點出隊,計數器加1,而後把下一層的節點所有入隊,直到遇到葉子節點或隊爲空。
/** * 求最小深度(非遞歸) */ public static int minDepth(TreeNode node){ if (node == null) return 0; Queue<TreeNode> queue = new LinkedList<>(); int level = 0; queue.add(node); while (!queue.isEmpty()){ level++; int levelnum = queue.size(); for(int i = 0; i < levelnum; i++){ TreeNode front = queue.poll(); if (front.left == null && front.right == null){ return level; //遇到第一個無葉子節點的時候,該節點的深度爲最小深度; } if (front.right != null){ queue.add(front.right); } if (front.left != null){ queue.add(front.left); } } } return level; }
根據二叉樹的前序或後序中的一個再加上中序來還原出整個二叉樹。
注意: 中序是必須有的,由於其能夠明確的把左右子樹分開。
先看下3種遍歷的特色(以下圖):
特色
前序的遍歷順序是根左右,中序的遍歷順序是左中右,
遞歸實現
1.前序的第一個節點是root節點,對應可以找到在中序中的位置。
2.根據中序遍歷的特色,在找到的根前邊序列是左子樹的中序遍歷序,後邊序列是右子樹的中序遍歷。
3.求出左邊序列的個數,好比設爲leftSize,那在前序序列中緊跟着根的leftSize個元素是左子樹的前序序列,後邊的爲右子樹的前序序列。
4.這樣就又得到了兩個子樹的前序遍歷和中序遍歷,開始遞歸。
/** * 根據前序遍歷和中序遍歷構造二叉樹; */ public static TreeNode buildTreeByPreOrder(int[] preorder, int[] inorder){ if (preorder == null){ return null; } //由於咱們要在中序遍歷中尋找某個元素的位置,而後劃分左右子樹 //用一個map來存儲元素在中序遍歷中的位置, Map<Integer,Integer> map = new HashMap<>(); for(int i = 0; i < inorder.length; i++){ map.put(inorder[i], i); } return buildTreeByPreOrder(preorder, inorder, 0, preorder.length-1, 0, inorder.length-1,map); } //傳入前序和中序,傳入前序的左右邊界,中序的左右邊界; private static TreeNode buildTreeByPreOrder(int[] preorder, int[] inorder, int preleft, int preright, int inleft, int inright, Map<Integer,Integer> map){ if (preleft > preright) return null; //得到整顆樹的根節點:先序中的第一個元素; TreeNode root = new TreeNode(preorder[preleft]); //獲得此元素在中序中的位置,以此進行劃分出左右子樹; int rootIndex = map.get(root); //獲得左子樹的大小;(注意此時不能直接是rootIndex,inleft不老是從0開始的,想一下創建右子樹的左子樹。 int leftTreeSize = rootIndex - inleft; //左子樹的中序:inleft不變,inright爲rootIndex-1; //左子樹的前序:preleft爲根後一位,即preleft+1,preright爲根後leftTreeSize位,即preleft+leftTreeSize; root.left = buildTreeByPreOrder(preorder,inorder,preleft+1, preleft+leftTreeSize, inleft, rootIndex-1, map); //右子樹的中序:inleft爲rootIndex+1,inright不變; //右子樹的前序:preleft爲左子樹的右邊界+1,即preleft+leftTreeSize+1,preright不變; root.right = buildTreeByPreOrder(preorder,inorder,preleft+leftTreeSize+1, preright, rootIndex+1, inright,map); return root; }
後序的遍歷順序是左右根,中序的遍歷順序是左根右,
遞歸實現
1.後序的第一個節點是root節點,對應可以找到在中序中的位置。
2.根據中序遍歷的特色,在找到的根前邊序列是左子樹的中序遍歷,後邊序列是右子樹的中序遍歷。
3.求出左邊序列的個數,好比設爲leftSize,那在後序序列中的leftSize個元素是左子樹的後序序列,後邊的爲右子樹的後序序列。
4.這樣就又得到了兩個子樹的後序遍歷和中序遍歷,開始遞歸。
/** * 根據後序遍歷和中序遍歷構造二叉樹 */ public static TreeNode buildTreeByPostOrder(int[] postorder, int[] inorder){ if (postorder == null){ return null; } //由於咱們要在中序遍歷中尋找某個元素的位置,而後劃分左右子樹 //用一個map來存儲元素在中序遍歷中的位置, Map<Integer,Integer> map = new HashMap<>(); for(int i = 0; i < inorder.length; i++){ map.put(inorder[i], i); } return buildTreeByPostOrder(inorder, postorder, 0, inorder.length-1, 0, postorder.length-1,map); } //傳入後序和中序,傳入後序的左右邊界,中序的左右邊界; private static TreeNode buildTreeByPostOrder(int[] inorder, int[] postorder, int inleft, int inright, int postleft, int postright, Map<Integer,Integer> map){ if (postleft > postright) return null; //得到整顆樹的根節點:後序中的最後個元素; TreeNode root = new TreeNode(postorder[postleft]); //獲得此元素在中序中的位置,以此進行劃分出左右子樹; int rootIndex = map.get(root.value); //獲得左子樹的大小;(注意此時不能直接是rootIndex,inleft不老是從0開始的,想一下創建右子樹的左子樹。 int leftTreeSize = rootIndex - inleft; root.left = buildTreeByPostOrder(inorder, postorder, inleft, rootIndex-1,postleft,postleft+leftTreeSize-1,map); root.right = buildTreeByPostOrder(inorder,postorder,rootIndex+1, inright, postleft+leftTreeSize,postright-1,map); return root; }
package xin.utils; import jdk.internal.dynalink.beans.StaticClass; import sun.reflect.generics.tree.VoidDescriptor; import javax.sound.midi.Soundbank; import java.awt.font.TransformAttribute; import java.util.*; public class Tree { } /** * 定義一個二叉樹節點; */ class TreeNode<T>{ public T value; //數據; public TreeNode<T> left; //左子樹; public TreeNode<T> right;//右子樹; public TreeNode(){} //空參的; public TreeNode(T value){ //有參的; this.value = value; } public TreeNode(T value, TreeNode left, TreeNode right){ this.value = value; this.left = left; this.right = right; } } class BinaryTreeTraverse<T> { /** * 前序遍歷(遞歸實現); */ public static void preOrderByRecursion(TreeNode node) { if (node == null) return; //遞歸終止條件; System.out.println(node.value); //獲取根節點的值; preOrderByRecursion(node.left); //左子樹的根節點; preOrderByRecursion(node.right);//右子樹的根節點; } /** * 前序遍歷(非遞歸實現); * 本質上就是維持遞歸實現的棧; * 1.先入棧根節點,輸出根節點的值,再入棧其右節點,左節點;(爲了出棧的時候先出左節點,再出右節點); * 2.出棧左節點,輸出值,再入棧左節點的右節點、左節點;直到遍歷完左子樹; * 3.出棧右節點,輸出值,再入棧右節點的右節點、左節點;直到遍歷完右子樹; * 每次都是出棧一個根節點,若是有孩子,就依次入棧其右節點和左節點。 * 規則: * 壓入根節點; * 1.彈出就打印; * 2.若有右孩子,壓入右; * 3.若有左孩子,壓入左;重複; * (至關於兩個孩子替換掉了棧裏的根節點,這就很符號:根先走了,左子樹幹到了最頂部,要是我左子樹還有孩子,ok,我也走,兩個孩子來替我, * 要是左子樹沒孩子了,我本身出去,我這裏就完事了;再去看右子樹就能夠了) */ public static void preOrder(TreeNode node) { Stack<TreeNode> stack = new Stack<>(); if (node != null) { stack.push(node); //根節點入棧; } while (!stack.isEmpty()) { TreeNode top = stack.pop(); //彈出就打印; System.out.println(top.value); if (top.right != null) stack.push(top.right); //依次入棧右節點和左節點; if (top.left != null) stack.push(top.left); } } /** * 中序遍歷(遞歸實現) */ public static void inOrderByRecursion(TreeNode node) { if (node == null) return; //遞歸終止; inOrderByRecursion(node.left); //先左子樹; System.out.println(node.value); //再根節點; inOrderByRecursion(node.right); //再右節點; } /** * 中序遍歷(非遞歸實現) * 規則: * 1.整條左邊界依次壓棧; * 2.條件1執行不了,彈出就打印; * 3.來到彈出節點右樹上,繼續執行條件1;(右樹爲空,執行2.彈出打印;右樹不爲空,執行1;壓棧) * 說明:將整個樹全用左邊界去看,都是先處理了左邊界,將左邊界分解成了左頭,頭先入,左再入,而後弄不動了,彈出,而後看其右節點,再把右節點裏的左依次進去;就這樣往返; */ public static void inOrder(TreeNode node) { Stack<TreeNode> stack = new Stack<>(); while (!stack.isEmpty() || node != null) { if (node != null) { //條件1;能往左走就往左走; stack.push(node); node = node.left; } else { TreeNode top = stack.pop(); //條件2;彈出打印; System.out.println(top.value); node = top.right; //條件3;來彈出節點右樹上,繼續1; } } } /** * 後序遍歷(遞歸實現) */ public static void postOrderByRecursion(TreeNode node) { if (node == null) return; postOrderByRecursion(node.left); postOrderByRecursion(node.right); System.out.println(node.value); } /** * 後序遍歷(非遞歸實現) * 想一下前序遍歷:根左右;過程是先壓右孩子,再壓左孩子; * 若是咱們想實現根右左:那就把前序裏的換成先壓左,再壓右;就處理成了 根右左; * 而後再從後往前看,就變成了右左根;因此能夠再準備一個棧,用來把第一個棧彈出的壓到第二個,那第二個彈出的時候就倒過來了; * 要記住:棧有實現倒序的功能; */ public static void postOrder(TreeNode node) { Stack<TreeNode> stackA = new Stack<>(); Stack<TreeNode> stackB = new Stack<>(); if (node != null) { stackA.push(node); } while (!stackA.isEmpty()) { TreeNode top = stackA.pop(); stackB.push(top); //棧A彈出的進入棧B;先實現根右左,B倒序實現左右根; if (top.left != null) stackA.push(top.left); if (top.right != null) stackA.push(top.right); } while (!stackB.isEmpty()) { System.out.println(stackB.pop().value); } } /** * Morris遍歷; * 二叉樹的結構中只有父節點指向孩子節點,孩子節點不能向上指,因此須要棧。 * 而morris遍歷的實質就是讓下層節點可以指向上層。怎麼辦呢,一個節點有兩個指針,左和右,若是這兩個指針上都有指向具體的節點確定就不行了。 * 可是二叉樹上有不少空閒指針,好比全部的葉子節點,它們的指針就指向null,因此能夠利用其right指針指向上層的節點。 * 這樣鏈接後,cur這個指針就能夠完整的順着一個鏈條遍歷完整個樹。 * 核心:以某個根節點開始,找到它左子樹的最右側節點(必然是個葉子節點),而後利用其right指向根節點(完成向上層的返回)。 * 到達兩次的是有左子樹的節點; * 到達一次的是沒有左子樹的節點; */ public static void morris(TreeNode node) { if (node == null) { return; } TreeNode cur = node; TreeNode mostRight = null; //cur左子樹的最右節點; while (cur != null) { mostRight = cur.left; if (mostRight != null) { //cur有左子樹; //找到左子樹的最右節點; while (mostRight.right != null && mostRight.right != cur) { mostRight = mostRight.right; //1.右邊爲空了證實到頭了(找到了);2.右邊指向上層了證實以前就處理過了,結束; } //走到這裏證實跳出上面循環,無非兩個緣由: //1.右邊沒了;2.右邊指向上層了(以前就處理過了); if (mostRight.right == null) { //右邊走到頭了; mostRight.right = cur; //左子樹的最右節點指向上層(cur); cur = cur.left; //cur左移,處理下一個節點; continue; //這次循環結束,開始下一個cur; } else { //證實這個mostRight的right指針已經處理過了,即mostRight已經指向了cur; // 能到這裏說明咱們已經回到了根節點,而且重複了以前的操做;也說明咱們已經徹底處理完了此根節點左邊的的樹了,把路斷開; mostRight.right = null; } } //cur右移的狀況: //1.cur沒有左子樹了(天然要開始處理右子樹) //2.cur有左子樹,可是cur左子樹最右節點已經指向cur了(執行完上面else斷開後,cur左邊已經徹底處理好了,開始右移。) cur = cur.right; } } /** * 前序遍歷(Morris實現); * 1.對於cur只到達一次的節點(沒有左子樹),cur到達就打印; * 2.對於cur到達兩次的節點,到達第一次時打印; */ public static void preOrderMorris(TreeNode node) { if (node == null) { return; } TreeNode cur = node; TreeNode mostRightNode = null; while (cur != null) { mostRightNode = cur.left; if (mostRightNode != null) { //到達兩次的節點; //找到cur左子樹的最右節點; while (mostRightNode.right != null && mostRightNode.right != cur) { mostRightNode = mostRightNode.right; } if (mostRightNode.right == null) { mostRightNode.right = cur; //指向上層cur; System.out.println(cur.value); //第一次到的時候打印; cur = cur.left; continue; } else { mostRightNode.right = null; //第二次到時不打印; } } else { System.out.println(cur.value); //只到達一次的節點; } cur = cur.right; } } /** * 中序遍歷(Morris實現); * 1.對於cur只到達一次的節點(沒有左子樹),cur到達就打印; * 2.對於cur到達兩次的節點,到達第二次時打印; */ public static void inOrderMorris(TreeNode node) { if (node == null) { return; } TreeNode cur = node; TreeNode mostRightNode = null; while (cur != null) { mostRightNode = cur.left; if (mostRightNode != null) { //有左子樹,到達兩次的節點; while (mostRightNode.right != null && mostRightNode.right != cur) { mostRightNode = mostRightNode.right; } if (mostRightNode == null) { mostRightNode.right = cur; //第一次不打印; cur = cur.left; continue; } else { System.out.println(cur.value); //第二次遇到時打印; mostRightNode.right = null; } } else { System.out.println(cur.value); //只到達一次的節點,遇到就打印; } cur = cur.right; } } /** * 後序遍歷(morris實現) * 後序遍歷比前面兩個要複雜一點; * 將一個節點的連續右節點當作是一個單鏈表來看待, * 當返回上層後,也就是將創建的連線斷開後,打印下層的單鏈表; * 單鏈表逆序打印,就和咱們作的把單鏈表逆序同樣。 */ public static void postOrderMorris(TreeNode node) { if (node == null) { return; } TreeNode cur = node; TreeNode mostRightNode = null; while (node != null) { mostRightNode = cur.left; if (mostRightNode != null) { while (mostRightNode.right != null && mostRightNode.right != cur) { mostRightNode = mostRightNode.right; } if (mostRightNode.right == null) { mostRightNode.right = cur; cur = cur.left; continue; } else { //能到這裏的都是達到過兩次的,也就是是有左孩子的。 mostRightNode.right = null; //這時候是已經返回上層以後,斷開了鏈接,因此打印下層的單鏈表; postMorrisPrint(cur.left); } } cur = cur.right; } postMorrisPrint(node); //最後把頭節點那一串右打印一遍; } public static void postMorrisPrint(TreeNode node) { TreeNode reverseList = postMorrisReverseList(node); //反轉鏈表; TreeNode cur = reverseList; while (cur != null) { System.out.println(cur.value); cur = cur.right; } postMorrisReverseList(reverseList); //最後再還原; } public static TreeNode postMorrisReverseList(TreeNode node) { TreeNode cur = node; TreeNode pre = null; while (cur != null) { TreeNode next = cur.right; cur.right = pre; pre = cur; cur = next; } return pre; } /** * 層次遍歷 * 藉助隊列的結構,每一層依次入隊,再依次出隊; * 對該層節點進行出隊操做時,須要將該節點的左孩子和右孩子入隊; */ public static void layerOrder(TreeNode node) { if (node == null) return; Queue<TreeNode> queue = new LinkedList<>(); queue.add(node); while (!queue.isEmpty()) { int size = queue.size(); //當前層的節點數量; for (int i = 0; i < size; i++) { TreeNode front = queue.poll(); System.out.println(front.value); if (front.left != null) queue.add(front.left); if (front.right != null) queue.add(front.right); } } } } class Depth{ /** * 求最大深度(遞歸) * 最大深度是左子樹和右子樹的最大深度的大的那個+1; */ public static int maxDepthByRecursion(TreeNode node){ if(node == null) return 0; int leftDepth = maxDepthByRecursion(node.left); int rightDepth = maxDepthByRecursion(node.right); return Math.max(leftDepth,rightDepth)+1; } /** * 求最大深度(非遞歸) * 層次遍歷(BFS) * 每遍歷一層,則計數器加+1;直到遍歷完成,獲得樹的深度。 */ public static int maxDepth(TreeNode node){ if (node == null) return 0; Queue<TreeNode> queue = new LinkedList<>(); int level = 0; //層數; queue.add(node); while (!queue.isEmpty()){ level++; int levelNum = queue.size(); //每層的節點數; for (int i = 0; i < levelNum; i++){ TreeNode front = queue.poll(); //當前層出隊,下一層入隊; if (front.left != null) queue.add(front.left); if (front.right != null) queue.add(front.right); } } return level; } /** * 求最小深度(遞歸) * 注意和求最大深度的區別; */ public static int minDepthByRecursion(TreeNode node){ if (node == null) return 0; if (node.right == null && node.left == null) return 1; if (node.left == null && node.right != null) return minDepthByRecursion(node.right) + 1; if (node.right == null && node.left != null) return minDepthByRecursion(node.left) + 1; return Math.min(minDepthByRecursion(node.left), minDepthByRecursion(node.right))+1; } /** * 求最小深度(非遞歸) */ public static int minDepth(TreeNode node){ if (node == null) return 0; Queue<TreeNode> queue = new LinkedList<>(); int level = 0; queue.add(node); while (!queue.isEmpty()){ level++; int levelnum = queue.size(); for(int i = 0; i < levelnum; i++){ TreeNode front = queue.poll(); if (front.left == null && front.right == null){ return level; //遇到第一個無葉子節點的時候,該節點的深度爲最小深度; } if (front.right != null){ queue.add(front.right); } if (front.left != null){ queue.add(front.left); } } } return level; } } class BuildBinaryTree{ /** * 根據前序遍歷和中序遍歷構造二叉樹; */ public static TreeNode buildTreeByPreOrder(int[] preorder, int[] inorder){ if (preorder == null){ return null; } //由於咱們要在中序遍歷中尋找某個元素的位置,而後劃分左右子樹 //用一個map來存儲元素在中序遍歷中的位置, Map<Integer,Integer> map = new HashMap<>(); for(int i = 0; i < inorder.length; i++){ map.put(inorder[i], i); } return buildTreeByPreOrder(preorder, inorder, 0, preorder.length-1, 0, inorder.length-1,map); } //傳入前序和中序,傳入前序的左右邊界,中序的左右邊界; private static TreeNode buildTreeByPreOrder(int[] preorder, int[] inorder, int preleft, int preright, int inleft, int inright, Map<Integer,Integer> map){ if (preleft > preright) return null; //得到整顆樹的根節點:先序中的第一個元素; TreeNode root = new TreeNode(preorder[preleft]); //獲得此元素在中序中的位置,以此進行劃分出左右子樹; int rootIndex = map.get(root.value); //獲得左子樹的大小;(注意此時不能直接是rootIndex,inleft不老是從0開始的,想一下創建右子樹的左子樹。 int leftTreeSize = rootIndex - inleft; //左子樹的中序:inleft不變,inright爲rootIndex-1; //左子樹的前序:preleft爲根後一位,即preleft+1,preright爲根後leftTreeSize位,即preleft+leftTreeSize; root.left = buildTreeByPreOrder(preorder,inorder,preleft+1, preleft+leftTreeSize, inleft, rootIndex-1, map); //右子樹的中序:inleft爲rootIndex+1,inright不變; //右子樹的前序:preleft爲左子樹的右邊界+1,即preleft+leftTreeSize+1,preright不變; root.right = buildTreeByPreOrder(preorder,inorder,preleft+leftTreeSize+1, preright, rootIndex+1, inright,map); return root; } /** * 根據後序遍歷和中序遍歷構造二叉樹 */ public static TreeNode buildTreeByPostOrder(int[] postorder, int[] inorder){ if (postorder == null){ return null; } //由於咱們要在中序遍歷中尋找某個元素的位置,而後劃分左右子樹 //用一個map來存儲元素在中序遍歷中的位置, Map<Integer,Integer> map = new HashMap<>(); for(int i = 0; i < inorder.length; i++){ map.put(inorder[i], i); } return buildTreeByPostOrder(inorder, postorder, 0, inorder.length-1, 0, postorder.length-1,map); } //傳入後序和中序,傳入後序的左右邊界,中序的左右邊界; private static TreeNode buildTreeByPostOrder(int[] inorder, int[] postorder, int inleft, int inright, int postleft, int postright, Map<Integer,Integer> map){ if (postleft > postright) return null; //得到整顆樹的根節點:後序中的最後個元素; TreeNode root = new TreeNode(postorder[postleft]); //獲得此元素在中序中的位置,以此進行劃分出左右子樹; int rootIndex = map.get(root.value); //獲得左子樹的大小;(注意此時不能直接是rootIndex,inleft不老是從0開始的,想一下創建右子樹的左子樹。 int leftTreeSize = rootIndex - inleft; root.left = buildTreeByPostOrder(inorder, postorder, inleft, rootIndex-1,postleft,postleft+leftTreeSize-1,map); root.right = buildTreeByPostOrder(inorder,postorder,rootIndex+1, inright, postleft+leftTreeSize,postright-1,map); return root; } }