--- title: '二元樹操作' disqus: kyleAlien --- 二元樹操作 === ## OverView of Content [TOC] ## 二元樹 - 基礎 二元樹基礎框架如下,接下來我們都會以框架出發,來解決問題 ```java= class TreeNode { TreeNode left, right; int val; public TreeNode(int val) { this.val = val; } } // 二元樹 遞迴框架 void traverse(TreeNode node) { traverse(node.left); traverse(node.right); } ``` ### 二元樹 - 走訪框架 * 二元樹框架如下,不同的題目會針對不同的走訪(前序、中序、後序) ```java= public class BaseModule { private static final class TreeNode { TreeNode left, right; int value; } public void recursive(TreeNode node) { // TODO: 前序走訪 recursive(node.left); // TODO: 中序走訪 recursive(node.right); // TODO: 後序走訪 } } ``` ### 多元樹 - 走訪框架 * 二元樹尋訪框架可以拓展為 N 元樹的尋訪框架 ```java= public class BaseModule { private static final class TreeNode { TreeNode[] children; int value; } public void recursive(TreeNode node) { for(TreeNode item : children) { // 遞迴 recursive(item); } } } ``` :::info * N 元樹尋訪又可稱為,**圖的尋訪**,因為 **圖就是好幾個 N 元樹的結合** > 出現環循環? 使用一個陣列標記已訪問過得節點即可 ::: ### 基礎控制 - Node 加一 * 以下我們先做一個簡單的二元樹,至於二元樹的建構規則我們下一節在說 ```java= public class TreeNode { TreeNode left, right; int val; public TreeNode(int val) { this.val = val; } public TreeNode(int val, TreeNode left, TreeNode right) { this(val); this.left = left; this.right = right; } public static TreeNode getBaseTreeNode() { TreeNode four = new TreeNode(4); four.left = new TreeNode(3); TreeNode two = new TreeNode(2, new TreeNode(1), four); TreeNode six = new TreeNode(6, null, new TreeNode(7)); return new TreeNode(5, two, six); } } ``` 建構出的二元樹圖如下 > ![](https://i.imgur.com/IxE7MQj.png) * 現在將上面二元樹上的 **每個節點 val + 1**,套用框架可以簡單的解決這件事 ```java= public class BaseControl { public void addEveryNode(TreeNode root) { // base case (遞迴都有一個 base case) if(root == null) { return; } System.out.print("Old: " + root.val); root.val += 1; System.out.println(", New: " + root.val); addEveryNode(root.left); addEveryNode(root.right); } public static void main(String[] args) { BaseControl baseControl = new BaseControl(); baseControl.addEveryNode(TreeNode.getBaseTreeNode()); } } ``` > ![](https://i.imgur.com/pzctpk1.png) ### 基礎控制 - 比較兩個樹 * 現在來比較兩個 Tree 是否相同,**同樣注意這裡的 base case** (**換位思考一下最底端,也就是末端時,你要如何處理方法的返回**) 1. 兩個 root 都為 null,返回 true > ![](https://i.imgur.com/IoOI2Mu.png) 2. 兩個 root 只有其中一個為 null,這就不相符,返回 false > ![](https://i.imgur.com/W2zOATD.png) 3. 兩者 Node 節點的數據不相同,這個不用說一定返回 false ```java= public class CompareTree { public boolean isSameTree(TreeNode root1, TreeNode root2) { if(root1 == null && root2 == null) { return true; } if(root1 == null || root2 == null) { return false; } if(root1.val != root2.val) { return false; } boolean leftTree = isSameTree(root1.left, root2.left); boolean rightTree = isSameTree(root1.right, root2.right); // 皆為 true return leftTree && rightTree; } public static void main(String[] args) { TreeNode tree1 = TreeNode.getBaseTreeNode(); TreeNode tree2 = TreeNode.getBaseTreeNode(); CompareTree compareTree = new CompareTree(); boolean result = compareTree.isSameTree(tree1, tree2); System.out.println("Is same tree: " + result); // ----------------------------------------------------------------- tree2.left = new TreeNode(1); boolean result2 = compareTree.isSameTree(tree1, tree2); System.out.println("Is same tree: " + result2); } } ``` > ![](https://i.imgur.com/ypsIJvw.png) ## 二元樹 - 規則 > 二元樹的排序是有規則的,按照規則來進行排序 1. **右子樹任意節點 (Node),要大於等於 ++左子樹++的 ==所有== 節點** 2. **左子樹任意節點 (Node),要小於等於 ++右子樹++的 ==所有== 節點** ### 二元樹 vs. Heap 規則 * Heap 也是使用二元分治法,不過它的規則 與 二元樹不同,**Heap 不是一個合法的二元樹** * 二元樹 vs. Heap 規則不同點 1. **入隊方式** * Heap:**從末端 Tail 入隊**,之後針對新數值進行 swim 上浮作業 * 二元樹:**從起始端 Header 入隊**,比較當前節點,**小於放置左側子樹、大於放置右測子樹** (最終可以達到左子樹全部節點都比右子樹任一節點小) > ![](https://i.imgur.com/TRG2auH.png) 2. **樹形狀** * Heap:限制必須是 **完全 or 滿二元樹** (一定會有左節點才會有右節點) * 二元樹:沒有限制,可以傾斜,不規則 > ![](https://i.imgur.com/7nyBPB8.png) 3. **Child Node 節點規則** * Heap:不可以比 Parent 節點大 * 二元樹:不可以比 Parent 節點大、**並且左子樹的所有節點都小於右子樹的任一節點** (Heap 無法達到這個規則) > ![](https://i.imgur.com/swwj2aR.png) ### 判斷 BST 合法性 - 錯誤 * 以下我們用框架,寫一個常常會發生問題的 二元樹合法性判斷,**由於該二元樹基於遞迴,所以一項先考慮 base case** > 以下範例是錯誤的判斷 ```java= // 有問題的 二元樹合法判斷 boolean isValidTree_Problem(TreeNode root) { // base case 1 if(root == null) { return true; } // base case 2 if(root.left != null && root.val <= root.left.val) { // 判斷 當前 Node 不該小於 小於等於 左 Node return false; } // base case 3 if(root.right != null && root.val >= root.right.val) { // 判斷 當前 Node 不該小於 大於等於 右 Node return false; } boolean leftTree = isValidTree_Problem(root.left); boolean rightTree = isValidTree_Problem(root.right); return leftTree && rightTree; } ``` * 這裡問題在於 Node 必須比較子樹的 **所有節點 (這個所有就是重點!),並符合條件**; 像是以圖就可以演示這個問題 > ![](https://i.imgur.com/9VjLsbZ.png) ```java= public static void main(String[] args) { CheckValid checkValid = new CheckValid(); TreeNode sub = new TreeNode(15, new TreeNode(9), new TreeNode(20)); TreeNode treeNode = new TreeNode(10, new TreeNode(2), sub); boolean result = isValid.isValidTree_Problem(treeNode); System.out.println("Is valid result: " + result); } ``` 這個測試結果有問題,這棵 Tree 應該不合法 ! > ![](https://i.imgur.com/mGphGZ4.png) 上述測試的問題點:**根結點 10 應該小於全部右子樹的節點 (這樣 9 就不符合)**,**++應該返回 false 才對++** > ![](https://i.imgur.com/mwE7T2r.png) ### 判斷 BST 合法性 - 輔助參數 - 正確 * 看到上面錯誤的範例,並且我們也發現了問題,就可以針對問題作修正 * 這邊我們使用 **輔助參數**,**增加方法函數的參數**,透過該參數進行判斷是否符合二元樹規則。為了解決這個問題 :::success * 這個參數是用來 **約束**:限制每個 Node 的區間,**有點類似於左右指針** 1. 對於左子樹:**右指針內縮 (限制最大值)**,也就是所有左子樹節點 node < root 2. 對於右子樹:**左指針內縮 (限制最小值)**,也就是所有右子樹節點 node > root ::: ```java= boolean isValidTree(TreeNode root, TreeNode min, TreeNode max) { if(root == null) { // 已到末端 return true; } if(max != null && root.val >= max.val) { return false; } if(min != null && root.val <= min.val) { return false; } // 傳入 root,限制最大值 boolean leftTree = isValidTree(root.left, min, root); // 傳入 root,限制最小值 boolean rightTree = isValidTree(root.right, root, max); // 左右子樹必須都符合,所以使用 `&&` return leftTree && rightTree; } ``` :::warning * 參數傳遞: 看似相同,但是差異在 **會傳遞 min、max**,比較的數值就會有 min、max 兩個 ! ::: > ![](https://i.imgur.com/n6tJ1Pg.png) ```java= public static void main(String[] args) { CheckValid checkValid = new CheckValid(); TreeNode sub = new TreeNode(15, new TreeNode(9), new TreeNode(20)); TreeNode treeNode = new TreeNode(10, new TreeNode(2), sub); // ----------------------------------------------------------------------------------- boolean result2 = checkValid.isValidTree(treeNode, null, null); System.out.println("Is valid result with 9: " + result2); sub.left = new TreeNode(11); boolean result3 = checkValid.isValidTree(treeNode, null, null); System.out.println("Is valid result with 13: " + result3); } ``` > ![](https://i.imgur.com/cLcctlX.png) ### BST 尋找指定數 - 遞迴 BST 二元搜尋,以下我們使用這棵樹 > ![](https://i.imgur.com/b2xbbKH.png) * 由於使用遞迴,所以重點仍先放在 ^1.^ base case (只要 base case 正確就對了一半),另一半是 ^2.^ 返回的結果 ```java= public class IsExist { public boolean isExist(TreeNode treeNode, int target) { // 找到末端仍沒找到 if(treeNode == null) { return false; } // 找到直接返回 if(treeNode.val == target) { return true; } boolean leftTree = isExist(treeNode.left, target); boolean rightTree = isExist(treeNode.right, target); // 返回使用 `||` (其中一個找到即可) return leftTree || rightTree; } public static void main(String[] args) { TreeNode baseTreeNode = TreeNode.getBaseTreeNode(); IsExist isExist = new IsExist(); boolean result = isExist.isExist(baseTreeNode, 4); System.out.println("Is 4 in tree ? " + result); boolean result2 = isExist.isExist(baseTreeNode, 99); System.out.println("Is 99 in tree ? " + result2); } } ``` * **使用 2 分法優化**:由於我們知道 2 元樹規則,比較 `root.val` & `target` 後就能完全排除 `target` 在另外一邊的可能性 ```java= public boolean isExist_Opt(TreeNode treeNode, int target) { if(treeNode == null) { return false; } if(treeNode.val == target) { return true; } if(treeNode.val > target) { return isExist(treeNode.left, target); } return isExist(treeNode.right, target); } public static void main(String[] args) { TreeNode baseTreeNode = TreeNode.getBaseTreeNode(); IsExist isExist = new IsExist(); boolean result = isExist.isExist_Opt(baseTreeNode, 4); System.out.println("Is 4 in tree ? " + result); boolean result2 = isExist.isExist_Opt(baseTreeNode, 99); System.out.println("Is 99 in tree ? " + result2); } ``` > ![](https://i.imgur.com/PJxYFsP.png) ### BST 尋找指定數 - 非遞迴 (迭代) BST 二元搜尋,以下我們使用這棵樹 > ![](https://i.imgur.com/b2xbbKH.png) * 除了上面的遞迴方法以外,我們也可以使用非遞迴的方式搜尋該二元樹 ```java= public boolean isExit_traverse(TreeNode treeNode, int target) { TreeNode tmp = treeNode; while (tmp != null) { if(tmp.val == target) { return true; } if(tmp.val > target) { tmp = tmp.left; } else { tmp = tmp.right; } } return false; } public static void main(String[] args) { TreeNode baseTreeNode = TreeNode.getBaseTreeNode(); IsExist isExist = new IsExist(); boolean result = isExist.isExit_traverse(baseTreeNode, 4); System.out.println("Is 4 in tree ? " + result); boolean result2 = isExist.isExit_traverse(baseTreeNode, 99); System.out.println("Is 99 in tree ? " + result2); } ``` > ![](https://i.imgur.com/nnAnQmW.png) ### BST 插入一個數 以下我們使用這棵樹 > ![](https://i.imgur.com/b2xbbKH.png) * **資料結構的操作,無非是尋訪 (找)、存取 (改)**;如果要改就是先進行 `找`,找到目標節點後,再進行 `改` (也就是插入) :::info 1. 一旦涉及 **改**:方法就 **必須返回 `TreeNode` 類型**,並 **接收遞迴呼叫地返回值** 2. 這裡也可以使用 **2 分概念** (分治法) ::: ```java= public class InsertNode { TreeNode insertNode(TreeNode root, int newVal) { if(root == null) { // 返回值會接到末端節點上 return new TreeNode(newVal); } if(root.val == newVal) { return root; } if(newVal < root.val) { root.left = insertNode(root.left, newVal); } if(newVal > root.val) { root.right = insertNode(root.right, newVal); } return root; } } ``` * 測試 `insertNode` 插入新數據 ```java= // 中序走訪 (測試用 private void recursivePrint(TreeNode node) { if(node == null) { return; } recursivePrint(node.left); System.out.print(node.val + ", "); recursivePrint(node.right); } public static void main(String[] args) { TreeNode baseTreeNode = TreeNode.getBaseTreeNode(); InsertNode t = new InsertNode(); t.insertNode(baseTreeNode, 10); t.insertNode(baseTreeNode, 8); t.recursivePrint(baseTreeNode); } ``` > ![](https://i.imgur.com/4eDPXyN.png) :::info * 以不同順序插入二元樹會對於最終二元樹圖形的結果有影響 如果以遞增順序插入,則效率會最差,會轉為一個 偏移傾斜二元樹;這時找尋一個數值就會耗費 O(N) 的時間 > ![](https://i.imgur.com/YpN4Sid.png) ::: ### BST 刪除指定 Node * BST 刪除指定 Node 就稍微複雜一點,依照規則需要考慮三種情況 (**如果是 N 元樹就有 N! 個狀況**) :::success 由於刪除也涉及 `改`,所以必須返回 `TreeNode` 類型 ::: ```java= // 套用基礎框架 public class DeleteNode { public TreeNode deleteNode(TreeNode root, int deleteKey) { if(root.val == deleteKey) { // TODO: 找到目標節點,準備進行刪除 return root; } if(root.val > deleteKey) { // 左子樹,串上刪除後的結果 root.left = deleteNode(root.left, deleteKey); } if(root.val < deleteKey) { // 右子樹,串上刪除後的結果 root.right = deleteNode(root.right, deleteKey); } return root; } } ``` 1. **刪除末端 Node**:這是一個 base case,重點是判斷左右節點都為 Node (代表是末端) ```java= // 左右皆為 null 代表末端 if(root.left == null && root.right == null) { return null; // 返回 null,就是斷開節點 } ``` > ![](https://i.imgur.com/9MR7Yxu.png) 2. **刪除 Node 有一個子樹**:把下一個子樹的數值全部複製 (有就是返回下一個子樹即可) ```java= // 左子樹不為空 if(root.left != null) { // root.val = root.left.val; // 手動交換 // root.left = root.left.left; // return root; return root.left; // 同上 `手動交換` } // 右子樹不為空 if(root.right != null) { // root.val = root.right.val; // 手動交換 // root.right = root.right.right; // return root; return root.right; // 同上 `手動交換` } ``` > ![](https://i.imgur.com/CAR8f1V.png) 3. **刪除 Node 有兩個子樹(難)**:**為了不破壞二元樹的規則**,以下有兩種方案 (都以刪除 `2` 這個 Node 為目標);**找左最大、找右最小,使用其中一個即可** * **左子樹:必須找到 ++左子樹中 ==最大==++ 的節點來交換** > 2 跟 1 交換 > > ![](https://i.imgur.com/5kjwHXB.png) ```java= // 左子樹方案 // 左右皆有子樹 if(root.left != null && root.right != null) { TreeNode maxNode = getMax(root.left); // 找左子樹中最大 root.val = maxNode.val; // 交換數值 (2 -> 1),就當作交換了節點 root.left = deleteNode(maxNode); // 刪除最小 (1) } TreeNode getMax(TreeNode node) { // 按照規則,最右邊的 Node 就是 BST 中最大的數 !!! // 如果沒有右節點,自然是當前的節點 while(node.right != null) { node = node.right; } return node; } ``` * **右子樹:必須找到 ++右子樹中 ==最小==++ 的節點來交換** > 2 跟 3 交換 > > ![](https://i.imgur.com/Qcrb7dt.png) ```java= // 右子樹方案 // 左右皆有子樹 if(root.left != null && root.right != null) { TreeNode minNode = getMin(root.right); // 找右子樹中最小 root.val = minNode.val; // 交換數值 (3 -> 4),就當作交換了節點 root.right = deleteNode(minNode); // 刪除最小 (3) } TreeNode getMin(TreeNode node) { // 按照規則,最左邊的 Node 就是 BST 中最小的數 !!! while(node.left ! null) { node = node.left; } return node; } ``` :::warning * 這裡用簡單的交換 val 值來代表節點已交換 ::: * 將上面的三種情況做整理再寫出完整的 BST 樹刪除指定節點 (以下使用找右子樹中最小) ```java= public class DeleteNode { public TreeNode deleteNode(TreeNode root, int deleteKey) { // base case if(root == null) { return null; } if(root.val == deleteKey) { // 目標結點是否是末端節點 if(root.right == null && root.left == null) { // 返回 null,讓原節點串上 return null; } // 有其中一個為 null if(root.left == null) { // 沒有左子樹 // 返回右子樹 return root.right; } if(root.right == null) { // 沒有右子樹 // 返回左子樹 return root.left; } // 有左、右子樹 TreeNode minNode = getMin(root.right); // 目前使用,找右子樹中最小 root.val = minNode.val; root.right = deleteNode(root.right, minNode.val); } if(root.val > deleteKey) { root.left = deleteNode(root.left, deleteKey); } if(root.val < deleteKey) { root.right = deleteNode(root.right, deleteKey); } return root; } private TreeNode getMin(TreeNode node) { while (node.left != null) { // 最小要往左找 ! node = node.left; } return node; } } ``` * 測試移除下圖中 `5` 這個節點 > ![](https://i.imgur.com/vXgL0L8.png) ```java= public static void main(String[] args) { TreeNode baseTreeNode = TreeNode.getBaseTreeNode(); DeleteNode deleteNode = new DeleteNode(); TreeNode result = deleteNode.deleteNode(baseTreeNode, 5); // 前序走訪 TreeNode.traverse(result); } ``` > ![](https://i.imgur.com/wDMdCzH.png) :::info * 另一種作法:請看註解 ```java= public class DeleteNode_2 { public TreeNode deleteNode(TreeNode root, int deleteKey) { // base case if(root == null) { return null; } if(root.val > deleteKey) { root.left = deleteNode(root.left, deleteKey); } else if(root.val < deleteKey) { root.right = deleteNode(root.right, deleteKey); } else if(root.val == deleteKey) { // 無左右子樹 if(root.right == null && root.left == null) { return null; } // 無左子樹 if(root.left == null) { // 返回右 return root.right; } // 無右子樹 if(root.right == null) { // 返回左 return root.left; } TreeNode tmp = root; // 先找到右子樹中最小 Node,準備重新串接 root = root.right; while (root.left != null) { root = root.left; } // 找到後移除右子樹的最小 // 將剛剛找到的最小 Node,並移除,重新串上右子樹 root.right = removeMin(tmp.right); // 將剛剛找到的最小 Node,重新串上左子樹 root.left = tmp.left; } return root; } // 移除最小 private TreeNode removeMin(TreeNode node) { if(node.left == null) { return node.right; } // 最終 node#left 左會串上 node#right node.left = removeMin(node.left); return node; } // 結果 public static void main(String[] args) { TreeNode baseTreeNode = TreeNode.getBaseTreeNode(); DeleteNode_2 d2 = new DeleteNode_2(); TreeNode result = d2.deleteNode(baseTreeNode, 5); TreeNode.traverse(result); } } ``` > ![](https://i.imgur.com/egZEHiW.png) ::: ## 二元樹分類 ### 完全二元樹 * 每一層都緊靠左邊,如果有其中一層 **無緊靠左側 (若沒有緊靠左側,就不是完整二元樹)**,則不是完全二元樹 > 類似於 Heap 的規則 > ![](https://i.imgur.com/i2s0YU3.png) :::success * 英文的完全二元樹是 **Complete Binary Tree** ::: ### 滿二元樹 * 每一層的元素都是滿的 > ![](https://i.imgur.com/4snGIZ9.png) :::success * 英文的滿二元樹是 **Perfect Binary Tree** ::: ## Node 數量 ### 普通二元樹 - Node 數量 計算以下二元樹 > ![](https://i.imgur.com/4h1sQ9K.png) ```java= public class NormalTree { public int getNormalTreeCount(TreeNode root) { if(root == null) { return 0; } int leftCount = getNormalTreeCount(root.left); int rightCount = getNormalTreeCount(root.right); return leftCount + rightCount + 1; // add self } public static void main(String[] args) { TreeNode treeNode = TreeNode.getBaseTreeNode(); NormalTree normalTree = new NormalTree(); int result = normalTree.getNormalTreeCount(treeNode); System.out.println("Normal tree count: " + result); } } ``` :::info * 只要經過一個節點,就將結點總數量 + 1 ::: **--實作結果--** > ![](https://i.imgur.com/N7MV7yC.png) ### 滿二元樹 - Node 數量 計算以下二元樹 > ![](https://i.imgur.com/iuX6tbM.png) * 滿二元樹可以跳脫框架進行更快速的計算,規則如下:**滿二元樹總結點數量為:==2^高度^ - 1==**,所以首先計算該樹的高,然後透過公式就可以取得滿二元樹的數量 :::info * 滿多元樹的 Node 總數量則是,(分支^高度^) - (分支 - 1) ::: ```java= public class PerfectTree { public int getPerfectTreeCount(TreeNode root) { int height = 0; while (root != null) { root = root.left; // same as right height++; } return (int) (Math.pow(2, height) - 1); } public static void main(String[] args) { PerfectTree perfectTree = new PerfectTree(); TreeNode leftTree = new TreeNode(2, new TreeNode(1), new TreeNode(4)); TreeNode rightTree = new TreeNode(7, new TreeNode(6), new TreeNode(8)); TreeNode tree = new TreeNode(5, leftTree, rightTree); int result = perfectTree.getPerfectTreeCount(tree); System.out.println("Perfect tree count: " + result); } } ``` **--實作結果--** > ![](https://i.imgur.com/2kM3NtH.png) ### 完全二元樹 - Node 數量 計算以下二元樹 > ![](https://i.imgur.com/fOaqrhq.png) * **完全二元樹,其實又跟滿二元樹相同。所以完全二元樹 = 普通二元樹 + 滿二元樹的測量** ```java= public class CompleteTree { public int getCompleteTree(TreeNode root) { TreeNode left = root; TreeNode right = root; int leftH = 0; while (left != null) { // 算左子樹最高 left = left.left; leftH++; } int rightH = 0; while (right != null) { // 算右子樹最高 right = right.right; rightH++; } // 完美二元樹 if(rightH == leftH) { System.out.println("Prefect"); return (int) Math.pow(2, rightH) - 1; } // `1` 是自身 return 1 + getCompleteTree(root.left) + getCompleteTree(root.right); } public static void main(String[] args) { CompleteTree completeTree = new CompleteTree(); TreeNode leftTree = new TreeNode(2, new TreeNode(1), new TreeNode(4)); TreeNode rightTree = new TreeNode(7, new TreeNode(6), null); TreeNode tree = new TreeNode(5, leftTree, rightTree); int result = completeTree.getCompleteTree(tree); System.out.println("Perfect tree count: " + result); } } ``` **--實作--** > ![](https://i.imgur.com/dP7yi3O.png) * **BIG O 大小:這種寫法的重點是從根結點開始的左、右子樹,只有一個會真正往下去做,另一個則會執行滿二元樹** 1. 遞迴演算法對於樹的高度為 `O(logN)` 2. 每次遞迴所花費的時間為 while 計算高度,計算高度的時間為 `O(2 * logN)` 3. 最終使用時間為 `O(2 * logN logN)` > ![](https://i.imgur.com/hGfKe87.png) ## 序列化 & 反序列化 BST ```java= public abstract class Code { // 序列化 public abstract String serialize(); // 反序列化 public abstract TreeNode deserialize(); } ``` * 反序列 String,使用前序走訪範例:`"5, null, null, null, 7, 6, null"` > ![](https://i.imgur.com/SCYcBrI.png) :::success * 序列化 ? 二元樹是一個二維的平面結構,而序列化後的字串是一個線性一維結構。所以 **序列化就是 ++將維度降為一維++**,**++重點仍是二元樹的尋訪++ !!** ::: :::danger * 反序列化要點 **要反序列化必須先建立 root 節點** ::: ### 前序走訪 preorder > **前序走訪從 左子樹的末端結點開始** 使用以下二元樹 > ![](https://i.imgur.com/rhpWWQN.png) * **Serialize 序列化:** 1. 套用框架並使用 **LinkedList** 實現 ```java= // 序列化 private final LinkedList<Integer> valList = new LinkedList<>(); public void traverse_List(TreeNode root) { if(root == null) { valList.addLast(-1); return; } valList.addLast(root.val); traverse_List(root.left); traverse_List(root.right); } public static void main(String[] args) { OrderBehind orderBehind = new OrderBehind(); TreeNode leftTree = new TreeNode(2, null, new TreeNode(4)); TreeNode rightTree = new TreeNode(3); TreeNode tree = new TreeNode(1, leftTree, rightTree); orderBehind.traverse_List(tree); System.out.println(orderBehind.valList); } ``` > ![](https://i.imgur.com/W05ADoR.png) 2. 套用框架,使用 **StringBuilder** 串接 ```java= // 序列化 public void traverse_String(TreeNode root) { if(root == null) { stringBuilder.append(NULL).append(SEP); return; } stringBuilder.append(root.val).append(SEP); traverse_String(root.left); traverse_String(root.right); } public static void main(String[] args) { OrderBehind orderBehind = new OrderBehind(); TreeNode leftTree = new TreeNode(2, null, new TreeNode(4)); TreeNode rightTree = new TreeNode(3); TreeNode tree = new TreeNode(1, leftTree, rightTree); orderBehind.traverse_String(tree); System.out.println(orderBehind.stringBuilder); } ``` > ![](https://i.imgur.com/4alc3wP.png) * **Deserialize 反序列化:** ```java= // 反序列化 private static final String SEP = ","; private static final String NULL = "null"; public TreeNode deserialize(String nodes) { LinkedList<String> list = new LinkedList<>(); for(String item : nodes.split(SEP)) { list.addLast(item); } return _deserialize(list); } private TreeNode _deserialize(LinkedList<String> list) { if(list.isEmpty()) { return null; } // 取出當前最前面的元素,代表了 root (僅限於前序) String first = list.removeFirst(); // NULL 代表到末端節點了 if(first.equals(NULL)) { return null; } TreeNode root = new TreeNode(Integer.parseInt(first)); root.left = _deserialize(list); root.right = _deserialize(list); return root; } ``` ### 後序走訪 postorder > **後序走訪 先走訪所有結點,才輪到父節點** * **Serialize 序列化**:序列化的方式也很簡單,套用模板後將序列化的方式寫在後續走訪的位置 ```java= public class PostOrder { private static final String SEP = ","; private static final String NULL = "null"; private final StringBuilder sb = new StringBuilder(); void serialize(TreeNode root) { if(root == null) { sb.append(NULL).append(SEP); return; } serialize(root.left); serialize(root.right); sb.append(root.val).append(SEP); } public static void main(String[] args) { PostOrder postOrder = new PostOrder(); TreeNode leftTree = new TreeNode(2, null, new TreeNode(4)); TreeNode rightTree = new TreeNode(3); TreeNode tree = new TreeNode(1, leftTree, rightTree); postOrder.serialize(tree); String s = postOrder.sb.toString(); System.out.println(s); } } ``` > ![](https://i.imgur.com/3OCeTb6.png) * **Deserialize 反序列化**:**這裡要特別注意,由於序列化時是使用 ++後序走訪++,所以在反序列化時,有以下特點** 1. **root 節點在尾端,需 ==從尾端取值==** 2. **由於從尾端開始取值,所以反序列化必須從 ==右到左==** ```java= // Deserialize public TreeNode deserialize(String data) { LinkedList<String> list = new LinkedList<>(); for (String item : data.split(SEP)) { list.addLast(item); } return _deserialize(list); } private TreeNode _deserialize(LinkedList<String> list) { if(list.isEmpty()) { return null; } // 從最後一個開始取值 ! String s = list.removeLast(); if(NULL.equals(s)) { return null; } TreeNode root = new TreeNode(Integer.parseInt(s)); // 先右再左 ! root.right = _deserialize(list); root.left = _deserialize(list); return root; } public static void main(String[] args) { PostOrder postOrder = new PostOrder(); TreeNode leftTree = new TreeNode(2, null, new TreeNode(4)); TreeNode rightTree = new TreeNode(3); TreeNode tree = new TreeNode(1, leftTree, rightTree); postOrder.serialize(tree); String s = postOrder.sb.toString(); System.out.println(s); TreeNode deserialize = postOrder.deserialize(s); } ``` ### 中序走訪 inorder * 中序走訪可以序列化,但是 **無法反序列化**,這是**因為 root 節點不在頭也不在尾,而是在中間,==無法確立 root 具體位置==** ! * **Serialize 序列化**: ```java= public class MiddleOrder { private static final String SEP = ","; private static final String NULL = "null"; private final StringBuilder sb = new StringBuilder(); public void serialize(TreeNode root) { if(root == null) { sb.append(NULL).append(SEP); return; } serialize(root.left); sb.append(root.val).append(SEP); serialize(root.right); } public static void main(String[] args) { MiddleOrder middleOrder = new MiddleOrder(); TreeNode leftTree = new TreeNode(2, null, new TreeNode(4)); TreeNode rightTree = new TreeNode(3); TreeNode tree = new TreeNode(1, leftTree, rightTree); middleOrder.serialize(tree); String s = middleOrder.sb.toString(); System.out.println(s); } } ``` > ![](https://i.imgur.com/SJvjQsm.png) ## Appendix & FAQ :::info ::: ###### tags: `資料結構`