創建二叉樹的方式,目前掌握的三種:
1、從先序
2、從層序
3、從先序+中序【LeetCode,劍指Offer07】
一、 思路
1、從先序創建二叉樹
/**
* 從先序建立二叉樹,0表示停止延伸。停止延伸時,葉子節點后面要有2個0才算停止延伸。
* 例1:
* 1
* 2 3
* 4 5
* 先序為,1 2 4 0 0 0 3 5 0 0 0
*
* 例2:
* 1
* 2 3
* 4 5
* 先序為,1 2 0 4 0 0 3 5 0 0 0
*
* 中序為,0 2 4 1 5 3 0 ?這個可以搞定嗎?感覺搞不定,你上來就空,讓人怎么接?
*
* 后序為,0 4 2 5 0 3 1 ?這個呢?
*
*/
2、從層序創建二叉樹
/**
* 從層序創建二叉樹,用的是下標。【不知道有沒有遞歸的方法???】
*
*
* 從層序創建二叉樹,0表示為空,以#結束。與上面的停止延伸還有所不同,葉子節點不用輸入2個0。
*
* 例子:
* 1
* 2 3
* 4 5
* 層序為,1 2 3 4 0 5 0
*
*
* 問題:
* 1、傳入的root沒有用到,而是新建的root,返回的也是新建的root。如果必須要用到傳入root的話,0要單獨處理!
* 2、log是以e為底的對數,如果以2為底,得除以log(2)。應該是(Math.log(n + 1) / Math.log(2)),而非 Math.log(n + 1)。
* 3、求2的冪次方,應該用Math.pow(2, i),不能用2^i。2^0是2,因為^是異或符號,不是多少次方!!!
*
*
* 【不行了,腦子不夠用了,撤吧,明天再搞!!!-1:53】
* 全部搞定了,開干,2021-8-1 22:01:09
* 終於OK啦!2021-8-2 03:13:01
*
*/
3、從先序+中序 創建二叉樹
之前做的,忘了。【有空再做一遍,字節面試中問過】
只記得:
前提:無重復節點。
1、先序的第一個是root。
2、在中序中查到root位置。中序的root左邊是左子樹,中序的root右邊是右子樹。
3、 先序的第二個開始是左子樹,長度由2可知。剩下的就是右子樹。
4、3中,先序左子樹第一個節點即左子樹的根就是root的左孩子,右子樹的第一個節點即右子樹的根就是root的右孩子。
二、代碼+輸出【完整】
包括代碼:
《二叉樹父子關系+推導》和《二叉樹四種遍歷:前序、中序、后序、層序。方法:遞歸、迭代、Morris》
1、代碼
package 數據結構.樹; import java.util.*; import java.util.stream.Collectors; public class TestTree { public static void main(String[] args) { // * 例1: // * 1 // * 2 3 // * 4 5 // * 先序為,1 2 4 0 0 0 3 5 0 0 0 // 層序為,1 2 3 4 0 5 0 // * // * 例2: // * 1 // * 2 3 // * 4 5 // * 先序為,1 2 0 4 0 0 3 5 0 0 0 // 層序為,1 2 3 0 4 5 0 //1 2 4 0 0 0 3 5 0 0 0 # //1 2 0 4 0 0 3 5 0 0 0 # //控制台輸入Queue: // Scanner in = new Scanner(System.in); // Queue<Integer> queue = new LinkedList<>(); // while (!in.hasNext("#")) { // queue.offer(in.nextInt()); // } BTNode root = new BTNode(); // 創建二叉樹 // 方法1:從先序中創建 // 直接int數組轉為Queue: // int[] arr = {1, 2, 4, 0, 0, 0, 3, 5, 0, 0, 0}; //【例1】 // int[] arr = {1, 2, 0, 4, 0, 0, 3, 5, 0, 0, 0}; //【例2】 // Queue<Integer> queue = Arrays.stream(arr).boxed(). // collect(Collectors.toCollection(LinkedList::new)); // root = createFromPre(root, queue); //從先序中創建 // 方法2:從層序中創建 int[] arr = {1, 2, 3, 4, 0, 5, 0}; //【例1】 // int[] arr = {1, 2, 3, 0, 4, 5, 0}; //【例2】 root = createFromLevel(arr); //從層序中創建 System.out.println("三種層序遍歷:"); System.out.println(levelOrderRecursion(new ArrayList<>(), root)); System.out.println(levelOrderIteration(new ArrayList<>(), root)); System.out.println(eachLevelOrderIteration(new ArrayList<>(), root)); System.out.println("三種先序遍歷:"); // System.out.println(preOrderTraversal(root)); System.out.println(preOrderRecursion(new ArrayList<>(), root)); System.out.println(preOrderIteration(new ArrayList<>(), root)); System.out.println(preOrderMorris(new ArrayList<>(), root)); System.out.println("三種中序遍歷:"); System.out.println(inOrderRecursion(new ArrayList<>(), root)); System.out.println(inOrderIteration(new ArrayList<>(), root)); System.out.println(inOrderMorris(new ArrayList<>(), root)); System.out.println("三種后序遍歷:"); System.out.println(postOrderRecursion(new ArrayList<>(), root)); System.out.println(postOrderIteration(new ArrayList<>(), root)); System.out.println(postOrderMorris(new ArrayList<>(), root)); } /** * 從先序建立二叉樹,0表示停止延伸。停止延伸時,葉子節點后面要有2個0才算停止延伸。 * 例1: * 1 * 2 3 * 4 5 * 先序為,1 2 4 0 0 0 3 5 0 0 0 * * 例2: * 1 * 2 3 * 4 5 * 先序為,1 2 0 4 0 0 3 5 0 0 0 * * 中序為,0 2 4 1 5 3 0 ?這個可以搞定嗎?感覺搞不定,你上來就空,讓人怎么接? * * 后序為,0 4 2 5 0 3 1 ?這個呢? * */ public static BTNode createFromPre(BTNode curr, Queue<Integer> queue) { //這個i有問題啊,根本沒法當指針用!你需要回傳i(return 2個值,要么用list,要么用對象), // 或者干脆用queue來控制!! int curr_val = queue.poll(); if (curr_val == 0) { return null; } curr.val = curr_val; curr.lc = new BTNode(); curr.rc = new BTNode(); curr.lc = createFromPre(curr.lc, queue); curr.rc = createFromPre(curr.rc, queue); return curr; } /** * 從層序創建二叉樹,用的是下標。【不知道有沒有遞歸的方法???】 * * * 從層序創建二叉樹,0表示為空,以#結束。與上面的停止延伸還有所不同,葉子節點不用輸入2個0。 * * 例子: * 1 * 2 3 * 4 5 * 層序為,1 2 3 4 0 5 0 * * * 問題: * 1、傳入的root沒有用到,而是新建的root,返回的也是新建的root。如果必須要用到傳入root的話,0要單獨處理! * 2、log是以e為底的對數,如果以2為底,得除以log(2)。應該是(Math.log(n + 1) / Math.log(2)),而非 Math.log(n + 1)。 * 3、求2的冪次方,應該用Math.pow(2, i),不能用2^i。2^0是2,因為^是異或符號,不是多少次方!!! * * * 【不行了,腦子不夠用了,撤吧,明天再搞!!!-1:53】 * 全部搞定了,開干,2021-8-1 22:01:09 * 終於OK啦!2021-8-2 03:13:01 * */ public static BTNode createFromLevel(int[] arr) { int n = arr.length; int h = (int) ((Math.log(n + 1) / Math.log(2)) - 1); //log是以e為底的對數,如果以2為底,得除以log(2) List<BTNode> bt = new ArrayList<>(); for (int i = 0; i <= h; i++) { //層編號i,從0開始,最大為h。 int num = (int) Math.pow(2, i); // 這一層的個數【按滿二叉樹】。 不能用2^i,2^0是2。因為^是異或符號,不是多少次方!!! int start = num - 1; // 從0開始。 int end = start + num - 1; // 2*num-2 //先把數都存進去 for (int j = start; j <= end; j++) { BTNode tmp = new BTNode(arr[j]); bt.add(tmp); } } //再連接父子節點 BTNode root = connectChilds(bt); return root; } /** * 把List中的所有節點連接為一棵樹。 * List[0]為root節點。 * 返回root。 * * 具體實現: * (1)為當前節點i連接左右孩子。i是編號,從0開始。 * (2)i的左孩子編號為1+2i,右孩子編號為2+2i。 * //連接父子,兩種辦法: // 1.計算父親的下標 // 大前提:編號從0開始! // 由2-結論,可以推出,編號j的父親編號為(j-1)/2。 // 由2-副結論,可以推出,第j個結點的父親是第j/2個結點。 // // 2.計算孩子下標 // 大前提:編號從0開始! // 結論:編號j的左孩子編號為1+2j。 // 副結論:第j個結點的左孩子是第2j個結點。 // // [推導過程] // // 當前j,求第一個孩子編號: // (1)第一個孩子編號=1+j+后面弟弟數+前面哥哥的孩子個數 (2)二叉樹,前面哥哥的孩子個數=哥哥數*2 // 當前在這一層中排第幾個至關重要: // (1)第k+1個,前面k個哥哥,后面num-k-1個弟弟。(2)k = j-前i層個數 = j-(num-1) // 第一個孩子編號 = 1 + j + (num-k-1) + k*2 = 1 + j + { num - [ j-(num-1) ] -1 } + [ j-(num-1) ] * 2 // = 1 + j + { num - [ j-num+1 ] -1 } + 2j-2num+2 // = 1 + j + { num - j + num - 1 -1 } + 2j-2num+2 // = 1 + j + { 2num - j - 2 } + 2j-2num+2 // = 1 + 2j // 結論:編號j的左孩子編號為1+2j。【這里的j是編號,是第j+1個結點!】 // // 副結論:第j個結點的左孩子是第2j個結點。 // 第j+1個結點的左孩子是第(2j+2)個結點。即第j個結點的左孩子是第2j個結點! // // 我也太棒了吧,我自己推導出來的!!!贊贊贊!!! * * @param list * @return */ private static BTNode connectChilds(List<BTNode> list) { for (int i = 0; (2 + 2 * i) < list.size(); i++) { BTNode cur = list.get(i); cur.lc = list.get(1 + 2 * i); cur.rc = list.get(2 + 2 * i); } return list.get(0); } /** * 層序,遞歸,按照每一層輸出 * * 由於遞歸方法levelOrderRecursion_Height()只能輸出第height層。 * 所以需要在外層加個循環,來遍歷所有的層。 * * height樹高,需要調用height()方法求。 * * @param list * @param root * @return */ private static List<Integer> levelOrderRecursion(List list, BTNode root) { int height = height(root); for (int i = 1; i <= height; i++) { List<Integer> tmp = new LinkedList(); list.add(levelOrderRecursion_Height(tmp, root, i)); } return list; } /** * 層序,遞歸【輸出第height層】 * * 思路:加個參數,樹高。 * * 這個只能打印第“height”層的節點。所以需要在外層加個循環,來遍歷所有的層。 * * @param list * @param root * @return */ private static List<Integer> levelOrderRecursion_Height(List<Integer> list, BTNode root, int height) { if (height == 1) { if (root != null) { // System.out.println(root.val); list.add(root.val); } else { // System.out.println("空"); list.add(null); } return list; } list = levelOrderRecursion_Height(list, root.lc, height - 1); list = levelOrderRecursion_Height(list, root.rc, height - 1); return list; } /** * 求樹高,遞歸 * * @param root * @return */ private static int height(BTNode root) { if (root == null) { return 0; } return Math.max(height(root.lc), height(root.rc)) + 1; } /** * 層序=廣度優先,迭代 * 使用隊列 * * 【應該沒寫完吧?】【OK了,之前出錯是存入左右孩子時沒有判空!現在沒問題。2021-8-1 17:26:54】 * * @param list * @param root * @return */ public static List<Integer> levelOrderIteration(List<Integer> list, BTNode root) { BTNode curr = root; Queue<BTNode> queue = new LinkedList(); queue.offer(curr); while (!queue.isEmpty()) { curr = queue.poll(); list.add(curr.val); // System.out.print(curr.val + " "); if (curr.lc != null) { queue.offer(curr.lc); } if (curr.rc != null) { queue.offer(curr.rc); } } return list; } /** * 層序,迭代,按照每一層輸出 * 使用兩個隊列,交替執行【用李喜旺牛腩飯中的蘿卜想通的】 * * 具體實現:不按照“交替”,而是按照“臨時-賦值”。 * 1、如果按照交替,兩個while代碼除了1和2交換,完全冗余。有划線,強迫症受不了。 * 2、按照臨時-賦值,每次都是先把queue1一個個往外出,孩子全都臨時存在queue2,最后queue1空之后,把queue2全都放回queue1,queue2清空。 * * 完美! 2021-8-1 17:10:46 * * @param list * @param root * @return */ public static List<Integer> eachLevelOrderIteration(List list, BTNode root) { BTNode curr = root; Queue<BTNode> queue1 = new LinkedList(); Queue<BTNode> queue2 = new LinkedList(); queue1.offer(curr); while (!queue1.isEmpty() || !queue2.isEmpty()) { LinkedList<Integer> tmpList1 = new LinkedList<>(); while (!queue1.isEmpty()) { curr = queue1.poll(); // System.out.print(curr.val + ","); tmpList1.add(curr.val); if (curr.lc != null) { queue2.offer(curr.lc); } if (curr.rc != null) { queue2.offer(curr.rc); } } // System.out.println(); list.add(tmpList1); queue1.addAll(queue2); queue2.clear(); } return list; } /** * 后序,遞歸 * @param list * @param root * @return */ private static List<Integer> postOrderRecursion(List<Integer> list, BTNode root) { if (root == null) { return list; } postOrderRecursion(list, root.lc); postOrderRecursion(list, root.rc); list.add(root.val); return list; } /** * 后序,迭代 * * 與先序迭代的區別: * 1、輸出時,新的要插到前面。這是為了保證后序,最后輸出根。 * 2、左右子樹入棧順序,是先左后右。因為插到前面順序正好與先序反着,先序是先右后左。 * * * @param list * @param root * @return */ private static List<Integer> postOrderIteration(List<Integer> list, BTNode root) { if (root == null) { return list; } Stack<BTNode> stack = new Stack<>(); stack.push(root); while (!stack.isEmpty()) { BTNode cur = stack.pop(); list.add(0, cur.val); if (cur.lc != null) { stack.push(cur.lc); } if (cur.rc != null) { stack.push(cur.rc); } } return list; } /** * 后序,Morris * * 先序DLR逆序輸出?不行!問題在輸出的是RLD,不是LRD!! * * 他人思路:【太妙了】 * 1、dump.lc = root: * cur從虛擬節點dump開始,而不是從root開始。。 * 這是為了把最后一個節點輸出,否則會剩下最右節點無法輸出,絕妙。 * (不加dump最右節點無法輸出,加上dump,dump無法輸出,相當於往后趕了一步) * * 2、輸出:大順序小逆序,從cur_lc到pre的逆序 * 在刪除索引后輸出從cur_lc到pre整個路徑的逆序,其他地方均不輸出。 * index保證,小序列內部逆序,但整體還是順序往大序列后添加的! * * * 理解兩方面:【不要妄想一口吃個胖子,先1后2,不急着先2】 * 1、知道他說的是啥:理解他如何實現,能走通數據結構。【主要看圖,走數據結構】 * 2、知道他為什么這么說:理解他設計的針對點,妙在哪里,怎么想出來的? * 怎么想出來的? * (1)dump。是發現最后一個節點無法輸出,再往下走一步就可以輸出了! * (2)逆序輸出cur_lc到pre。【這個還不知道怎么想的?】 * (3)index。這個是發現應該大順序,小逆序,如果不用index,就全是逆序,也不行。 * * @param list * @param root * @return */ private static List<Integer> postOrderMorris(List<Integer> list, BTNode root) { BTNode dump = new BTNode(); dump.lc = root; BTNode cur = dump; BTNode pre = null; while (cur != null) { if (cur.lc == null) { cur = cur.rc; } else { pre = cur.lc; while (pre.rc != null && pre.rc != cur) { pre = pre.rc; } if (pre.rc == null) { pre.rc = cur; cur = cur.lc; } else { int index = list.size(); //這個index可以保證,每次添加的小序列內部是逆序,但是大序列中還是往整個list后添加的! BTNode cur_lc = cur.lc; while (cur_lc != pre) { list.add(index, cur_lc.val); //這個之前錯弄成了cur.val cur_lc = cur_lc.rc; } list.add(index, pre.val); //終止條件之后,再加進去一個pre。 pre.rc = null; cur = cur.rc; } } } return list; } /** * 中序,遞歸 * * @param list * @param root * @return */ private static List<Integer> inOrderRecursion(List<Integer> list, BTNode root) { if (root == null) { return list; } inOrderRecursion(list, root.lc); list.add(root.val); inOrderRecursion(list, root.rc); return list; } /** * 中序,迭代 * * 1、左鏈入棧:先把左子樹全部入棧,直到最左邊的葉子。 * 2、棧不空時,彈出一個節點,輸出val。 * 3、無論棧空不空,每次都要對右孩子繼續做“左鏈入棧”。 * 4、循環退出條件,stack空且當前節點為空。 * * @param list * @param root * @return */ private static List<Integer> inOrderIteration(List<Integer> list, BTNode root) { Stack<BTNode> stack = new Stack(); while (!stack.isEmpty() || root != null) { while (root != null) { stack.push(root); root = root.lc; } if (!stack.isEmpty()) { root = stack.pop(); list.add(root.val); } //其實這個地方,有點像Morris的方法,只不過Morris用了葉子的指針存,而不是stack。 // 一旦彈出一個節點,對右孩子繼續做“左鏈入棧” root = root.rc; } return list; } /** * 中序,Morris * 其實也是迭代,不用棧的特殊迭代。 * * 把二叉樹拉直成鏈表。【也不是】 * 把rc當成鏈表的next指針。 * * 2、我的理解:Morris是用時間換空間。其實還不如用stack呢!【錯了】 * * 一直往左走,每次左不空就先標索引(左子樹最右節點為pre),往左走。cur=cur.lc。 * 直到左為空,就把cur輸出, * 如果cur不是葉子,就是往右子樹走。cur=cur.rc。 * 如果cur是葉子,就是根據索引返回父節點。仍然是cur=cur.rc。 * * 返回父節點后,再查一次pre,發現pre.rc=cur,則輸出當前父親(cur),並且把索引刪掉。 * 然后就遍歷右子樹啦。cur=cur.rc。 * * 2個地方輸出:輸出后都是往右走,因為輸出的是中。 * 左空,輸出cur,往右走/返回父親,都是cur=cur.rc。 * 左不空,且pre.rc==cur,輸出cur,往右走cur=cur.rc。 * * 往左走: * 只要左不空就標索引pre.rc=cur,標完索引往左走cur=cur.lc。 * * * 3、空間和時間復雜度: * 空間復雜度為O(1):不用棧而是借助葉子的右指針不費空間,只用到了一個pre用O(1)。 * 時間復雜度是O(n): * 每次查詢所謂“前驅節點”其實就是找到左子樹的最右節點,每個節點都要查兩遍左子樹,一遍是加索引,一遍是刪索引。 * 看似涉及樹高度,是O(nlgn),實際上是每條邊最多走兩遍,是O(2n)也就是O(n)。 * * * @param list * @param root * @return */ private static List<Integer> inOrderMorris(List<Integer> list, BTNode root) { BTNode cur = root; BTNode pre = null; while (cur != null) { if (cur.lc == null) { list.add(cur.val); cur = cur.rc; } else { pre = cur.lc; while (pre.rc != null && pre.rc != cur) {//找到最右節點,即為pre。 pre = pre.rc; } if (pre.rc == null) { pre.rc = cur; cur = cur.lc; } else { // pre.rc == cur pre.rc = null; list.add(cur.val); //與先序唯一的不同之處,是在刪索引的時候輸出!!! cur = cur.rc; } } } return list; } /** * 這個純粹是為了迎合Offer07的參數列表要求 * @param root * @return */ public static List<Integer> preOrderTraversal(BTNode root) { List<Integer> list = new ArrayList(); list = preOrderRecursion(list, root); return list; } /** * 先序,遞歸 * * @param list * @param root * @return */ public static List<Integer> preOrderRecursion(List<Integer> list, BTNode root) { if (root == null) { return list; } list.add(root.val); list = preOrderRecursion(list, root.lc); list = preOrderRecursion(list, root.rc); return list; } /** * 先序,迭代 * * 竟然用棧來實現,而且是先右后左!我驚呆了!!! * 我一直以為是隊列。。。。 * * @param list * @param root * @return */ public static List<Integer> preOrderIteration(List<Integer> list, BTNode root) { if (root == null) { return list; } Stack<BTNode> stack = new Stack<>(); stack.push(root); while (!stack.isEmpty()) { BTNode curr = stack.pop(); list.add(curr.val); if (curr.rc != null) { stack.push(curr.rc); } if (curr.lc != null) { stack.push(curr.lc); } } return list; } /** * Morris的先序方法,也是迭代。 * * 與中序唯一的不同之處,是在加索引前輸出!!! * * @param list * @param root * @return */ public static List<Integer> preOrderMorris(List<Integer> list, BTNode root) { BTNode cur = root; BTNode pre = null; while (cur != null) { if (cur.lc == null) { list.add(cur.val); cur = cur.rc; } else { pre = cur.lc; while (pre.rc != null && pre.rc != cur) { pre = pre.rc; } if (pre.rc == null) { list.add(cur.val); //與中序唯一的不同之處,是在加索引前輸出!!! pre.rc = cur; cur = cur.lc; } else { //pre.rc == cur pre.rc = null; cur = cur.rc; } } } return list; } } class BTNode { int val; BTNode lc; BTNode rc; public BTNode() { } public BTNode(int val) { this.val = val; this.lc = null; this.rc = null; } } /** * 不限子節點個數的一般樹,用左孩子和右兄弟 */ class GeneralTreeNode { int val; GeneralTreeNode left_child; GeneralTreeNode right_sibling; }
2、輸出結果:
三種層序遍歷: [[1], [2, 3], [4, 0, 5, 0]] [1, 2, 3, 4, 0, 5, 0] [[1], [2, 3], [4, 0, 5, 0]] 三種先序遍歷: [1, 2, 4, 0, 3, 5, 0] [1, 2, 4, 0, 3, 5, 0] [1, 2, 4, 0, 3, 5, 0] 三種中序遍歷: [4, 2, 0, 1, 5, 3, 0] [4, 2, 0, 1, 5, 3, 0] [4, 2, 0, 1, 5, 3, 0] 三種后序遍歷: [4, 0, 2, 5, 0, 3, 1] [4, 0, 2, 5, 0, 3, 1] [4, 0, 2, 5, 0, 3, 1]
問題:
1、從層序創建二叉樹,目前是用下標。有沒有遞歸/迭代的方法?
2、層序,有沒有Morris方法?
3、可以從中序創建二叉樹嗎?后序呢?
4、從先序+中序 創建二叉樹?
