# 資結(下)- 課輔 [TOC] ## 說明 ### 時間 - 每個星期一的 20:00~22:00 ### 內容 - 以老師上課的範圍為主,再幫同學複習一次全部的內容 - 總共有 4 個章節,預計每 2 次課輔講完一章。 - 每次課輔 2 小時,預計 1.5 小時講上課內容,剩餘時間讓同學提問。 - 程式碼會以 java 為主 ### 老師 moddle 的課程大綱 - ![image](https://hackmd.io/_uploads/HkNyIISWA.png) ## Tree ### 定義 - tree 是由 1~n 個 node 組成的集合 - 有 1 個特定 node 作為 root,且此 node 沒有 parent - 其餘的 node 組成 x 個互斥集合(subtree) - node - parent node - child node - leaf/terminal node - 沒有 child node - sibling - node 間有相同 parent - tree degree - tree 的分支度 - 一個 tree 的 node 中有的最大分支度就是此 tree 的分支度 - tree height/depth - 一般 root 會從 1 開始計算,少數從 0 開始 - forest - 由 0~N 個互斥的 tree 組成 - tree 的表示方式 1. linked list - 每個 node 除了自身的 data 外,還有 k 個 link(k = degree),用於連到其他 node - node structure | data | Link 1 | Link 2 | Link k | | -------- | -------- | -------- | -------- | - 缺點 - 浪費空間存無用的 link - 共需 n*k link 的空間存 link,但實際上有存 node 的 link 只有 n-1 個 - 故使用率只有 (n-1) / n*k ≒ 1 / k - 會剩下很多空 link,浪費空間,且當 k 越大,浪費的空間越多 3. 轉為 binary tree - binary tree 的 degree 最大為 2 - 所以可以確保使用率可達到接近 1/2 5. 括號法 - 用括號和巢狀關係表示 parent and child node - node structure - 父(子) - ex. - ![image](https://hackmd.io/_uploads/ryF54vsbC.png) - A(B(EF)C(G)D) ### binary tree - 定義 - 由 root, left subtree, right subtree 組成 - left, right subtree 亦是 binary tree - 可以為空樹(root 為空) - degree 最大只能為 2 - node 有左右之分 - 3 大定理 1. 第 i level 的 node 數量最多共為 2^(i - 1) 2. 高度為 k 的 binary tree, 總 node 數最多為 2^k - 1 3. 一非空的 binary tree, 若 n0 = leaf node 數量, n2 = degree 為 2 的 node 數量,則 n0 = n2 + 1 - 練習 1. binary tree 有用的 link 數量為 99,則共有多少 node 2. binary tree 有 n 個 node, 則最高和最低的 level 為多少 3. binary tree 有 12 個 node, 且 degree = 1 的 node 有 5 個,則 leaf node 數量有幾個 - 特殊 binary tree 1. skewed binary tree - 分為左斜曲和右斜曲 - ![image](https://hackmd.io/_uploads/rJFNtPobR.png) 2. full binary tree - 當 tree 高度為 k, 總 node 為 2^k - 1 - ![image](https://hackmd.io/_uploads/HyLoYvibR.png) 3. complete binary tree - 將高度為 k 且總 node 為 n 的 tree 的 node 依照 level 依序編號後,會與高度一樣為 k 的 full binary tree 的前 n 個 node 的編號對應 - ![image](https://hackmd.io/_uploads/HkRjqPiZA.png) - 定理 - 有一編號為 i 的 node 1. 其 left child 編號為 2i, 若 2i <= n 2. 其 right child 編號為 2i+1, 若 2i+1 <= n 3. 其 parent 編號為 [i/2], 若 [i/2] > 1 4. degree = 1 的 node 數量為 1 or 0 - ![image](https://hackmd.io/_uploads/Sk1PjDjW0.png) - 練習 - complete binary tree 有 1000 nodes 1. 最後一個 parent node 的編號 2. 編號 250, 251 的 node 是否為 sibling 3. 編號 250 的祖父 node(parent 的 parent) 4. 編號 371 的 left, right, parent node 5. 編號 512 的 left node 6. 此樹的 depth 7. degree = 1 的 node 數量 4. strict binary tree - 所有 non-leaf node 的 degree 皆為 2 - ![image](https://hackmd.io/_uploads/HJcdudjWA.png) - 練習 1. full binary tree 一定是 strict binary tree ? 2. complete binary tree 一定是 strict binary tree ? 3. strict binary tree 一定是 full binary tree ? 4. strict binary tree 一定是 complete binary tree ? - binary tree 表示法 1. linked list - node structure | LChild | data | RChild | | -------- | -------- | -------- | - ex. - ![image](https://hackmd.io/_uploads/B1D5FOsZ0.png) - 優點 - insert, delete node 方便 - 缺點 - 不易知道 parent - 浪費約一半的 link 存空值 2. array - 若 tree 的高度為 k, 則準備一個 1-D array, Arr[1 ~ 2^k-1] - ![image](https://hackmd.io/_uploads/SkMyoOs-0.png) - 優點 - 可容易用 index 知道 parent - 不須 link space - 缺點 - insert, delete 不便 - 不適用於 skewed tree (浪費太多空間) - binary tree traversal - 走訪 binary tree 的所有 node 一次,且 L 要在 R 之前走訪 - ![image](https://hackmd.io/_uploads/HyLSh_jWC.png) - 前序 - DLR - code ```java= class Node { int data; // data of node Node LChild; // left child Node RChild; // right child } void preorder(Node n) { if (n != null) { System.out.println(n.data); preorder(n.LChild); preorder(n.RChild); } } ``` - 中序 - LDR - code ```java= void inorder(Node n) { if (n != null) { preorder(n.LChild); System.out.println(n.data); preorder(n.RChild); } } ``` - 後序 - LRD - code ```java= void inorder(Node n) { if (n != null) { preorder(n.LChild); preorder(n.RChild); System.out.println(n.data); } } ``` - 練習 - 求以下 binary tree 的 前中後序 1. ![image](https://hackmd.io/_uploads/Hy492uiZC.png) :::spoiler ANS 前 : ABDCEGLF 中 : BDAGELCF 後 : DBGLEFCA ::: 3. ![image](https://hackmd.io/_uploads/rkp52usWR.png) :::spoiler ANS 前 : LMNOQPRS 中 : MQONPSRL 後 : QOSRPNML ::: - 給定 前序+中序 or 後序+中序,找出唯一的 binary tree 1. 前 : ABDCEGLF;中 : BDAGELCF :::spoiler ANS ![image](https://hackmd.io/_uploads/r1CtkKsWC.png) ::: 3. 後 : QOSRPNML;中 : MQONPSRL :::spoiler ANS ![image](https://hackmd.io/_uploads/r1ms1Fo-0.png) ::: - 舉例給定 前序+後序,無法找出唯一的 binary tree :::spoiler ANS 前 : ABC;後 : CBA ![image](https://hackmd.io/_uploads/HJcJeFobC.png) ::: - binary tree 的應用 1. expression tree - 利用 binary tree 表達運算式 - 規範 - non-leaf 代表 operator - leaf 代表 operand - prioity 越高的 operand 會在 tree 的越下層(代表先被執行) - ex. 1. A + B * C - D / E :::spoiler ANS ![image](https://hackmd.io/_uploads/Bkc6IYsZ0.png) ::: 2. 給一 expression tree, 回推 expression ![image](https://hackmd.io/_uploads/BJQUvts-C.png) :::spoiler ANS A + B * C - D / E ::: ### binary search tree - 為一 binary tree, 可為空樹 - 若不為空,則滿足: - root > left child 的值 - root < right child 的值 - left and right subtree 亦是 binary search tree - 用中序走訪可得到由小到大排序的 data - ex. - ![image](https://hackmd.io/_uploads/Sy1PNcj-R.png) - 練習 1. 依序將下列 data 建立 binary search tree, 並用中序走訪建完的樹 : 26, 5, 77, 8, 19, 31, 17, 45 :::spoiler ANS ![image](https://hackmd.io/_uploads/Sy9345o-R.png) ::: 2. binary search tree 的後序 : ACBFKPLD。求此樹的前序 :::spoiler ANS 前序 : DBACLKFP ::: - search - 搜尋一值是否存在於一 binary search tree 平均只需 O(logn) - 若欲找值小於 root, 則往 left subtree 繼續找,反之往 right subtree 找 - code ```java= boolean search(Node n, int data) { if (n == null) { // 欲找的 data 不存在於 tree return false; } else { if (n.data == data) { return true; } else if (n.data > data) { return search(n.LChild, data); } else { return search(n.RChild, data); } } } ``` - delete - 欲在一 binary search tree 中刪除一 node 有兩種狀況 1. 該 node 為 leaf node : 直接刪除 2. 該 node 為 non-leaf node - 有 1 個 child : child 直接取代 - 有 2 個 child : 有兩種方式,都可以用 - 拿 left subtree 的最大值取代 - 拿 right subtree 的最小值取代 - 練習 - ![image](https://hackmd.io/_uploads/B1ED1hs-R.png) 1. delete 30 :::spoiler ANS 直接刪除 30 ::: 3. delete 80 :::spoiler ANS 70 取代 80 ::: 5. delete 50 :::spoiler ANS 20 取代 50 60 取代 50 ::: ### Thread binary tree - 若 node 的 LChild/RChild 為空,則視為 左/右引線。引線會指向中序中 node 的 前/後一個 node - 優點 - 充分利用 n+1 條空 link - 簡化中序追蹤,無須遞迴和 stack - 容易找到中序前後繼者 - node structure | LeftThread | LChild | data | RChild | RightThread | | -------- | -------- | -------- | -------- | --------| - LeftThread : true, 左 link 為 thread;反之為 false - RightThread : true, 右 link 為 thread;反之為 false - 在 thread binary tree 需額外引入一個串列首,又分為兩種狀況 1. 空樹 - ![image](https://hackmd.io/_uploads/rkcawhsZA.png) 2. 非空樹 - ![image](https://hackmd.io/_uploads/B1MJ_2sZA.png) - ex. 以下 binary tree 用 thread binary tree 表示 - ![image](https://hackmd.io/_uploads/H1fGd2sWR.png) - ![image](https://hackmd.io/_uploads/rJtw_2j-C.png) - 找 node 的中序 前/後繼者 - 後繼者:分為 2 種狀況 1. node 的 RightThread = true - 則 node 的 RChild 為後繼者 2. otherwise - 設 node 的 RChild 為 X - 找到 X 的最後一個非空的 LChild node Y - 則 Y 的 LChild 為後繼者 - code ```java= Node InSuc(Node n) { Node temp = n.RChild; if (temp.RightThread == false) { // 不是引線 while (temp.LChild != null) { // 找到最左兒子 temp = temp.LChild; } } return temp; } ``` - 前繼者 - 和找後繼者的方式一樣,差在方向相反 - code ```java= Node PreSuc(Node n) { Node temp = n.LChild; if (temp.LeftThread == false) { // 不是引線 while (temp.RChild != null) { // 找到最右兒子 temp = temp.RChild; } } return temp; } ``` ## Heap ## Advanced Tree ### AVL - 概念 - 動態平衡,維持高度為 logn,避免成為 skewed tree - search, insert, delete 皆為 O(logn) - 定義 - binary search tree - 左右子樹亦是 AVL tree - node 的 balance factor 會是 -1, 0, 1 - 動態平衡調整方法 1. LL 2. LR 3. RL 4. RR - 定理 - 高度為 h 的 avl tree, 最少 node 數為 Fh+2 - 1 - 費氏數列 - f0 = 0, f1 = 1 - fh = fh-1 + fh-2 - 練習 1. 欲形成高度為 5 的 AVL tree - 最少 node 數 : 12 - 最多 node 數 : 31 2. 有一 AVL tree, node 數 = 15, 求最大高度 - 5 ### m-way search tree - 通常 tree degree > 2 - 以空間換時間 ### B-tree - 定義 - balanced m-way search tree - root 至少要有 2 個 child - 除了 root 和 leaf 以外,其餘 node degree m/2~m 之間 - 所有 leaf, failure node 都在同一層 - 2-3 tree - m=3 - degree : 2~3, if not root or leaf - 2-3-4 tree - m=4 - degree : 2~4, if not root or leaf - insert - 找到插入位置 - check overflow (key 數 > m-1) - 若有 overflow, 則 split - split : 將第 m/2 個 key 作為 parent, 其餘 key 分裂為 2 個 node - delete - 要刪除 X key, 有兩種狀況 1. leaf - 直接把 X 刪除 - check underflow (key 數 < m/2 - 1), 做調整 1. 看是否可以做 rotation (借 sibling node 的一個 node) 2. 若無法,做 combination (合併 parent and sibling node) 2. non-leaf - 找左子樹的 max or 右子樹的 min 取代 - 取代完後 check underflow 1. 看是否可以做 rotation (借 sibling node 的一個 node) 2. 若無法,做 combination (合併 parent and sibling node) ## Graph ### 定義 - G = <V,E> - 圖形(Graph)由頂點(Vertex)集合、邊(Edge)集合所組成 - ex. - ![image](https://hackmd.io/_uploads/HJLGurIzC.png) - V = {1,2,3}, E = {(1,2), (2,3), (1,3)} ### 種類 1. Undirected Graph - 定義 - G = <V,E>, 若 vi, vj 屬於 E,則 (vi, vj) = (vj, vi) - ex. - ![image](https://hackmd.io/_uploads/HJLGurIzC.png) 2. Directed Graph (digraph) - 定義 - G = <V,E>, 若 vi, vj 屬於 E,則 (vi, vj) != (vj, vi) - ex. - ![image](https://hackmd.io/_uploads/r1rouSIzA.png) ### 常用術語 1. complete graph - undirected graph - 定義 - V = n, 有 n(n-1) / 2 個邊 - 圖形中的任一頂點,可直接用一條邊到其他頂點 - ex. - ![image](https://hackmd.io/_uploads/HJMcKBUzA.png) - directed graph - 定義 - V = n, 有 n(n-1) 個邊 - 圖形中的任一頂點,可直接用一條邊到其他頂點 - ex. - ![image](https://hackmd.io/_uploads/BJ5H9BLMA.png) 3. subgraph - ex. - ![image](https://hackmd.io/_uploads/rJadiHUfR.png) 5. path - 定義 - 由邊集合組成 - ex. - ![image](https://hackmd.io/_uploads/B1TZhHUfR.png) 7. path length - 定義 - path 中的邊的數量 - ex. - ![image](https://hackmd.io/_uploads/SJbrnSUfR.png) 9. simple path - 定義 - 除起點和終點可相同外,其餘路徑中不可經過相同頂點 - ex. - ![image](https://hackmd.io/_uploads/BkiS6SIfR.png) 11. cycle - 定義 - 為 simple path, 且起點、終點需相同 - ex. - ![image](https://hackmd.io/_uploads/HkqbRBLGC.png) 13. connected - 定義 - 一 undirected graph 中,所有頂點都有 path 可以到達其他頂點 - ex. - connected - ![image](https://hackmd.io/_uploads/ByqaRrUGC.png) - unconnected - ![image](https://hackmd.io/_uploads/ByukJIIM0.png) 15. connected components - ex. - ![image](https://hackmd.io/_uploads/S12D1UUGC.png) 17. strongly connected - 定義 - 一 directed graph 中,所有頂點都有 path 可以到達其他頂點 - ex. - not strongly connected - ![image](https://hackmd.io/_uploads/S1PVe8UGR.png) - strongly connected - ![image](https://hackmd.io/_uploads/r1BreILMR.png) 19. degree - digraph - 有分為 in-degree and out-degree - ex. - ![image](https://hackmd.io/_uploads/Sk5-Z8UfR.png) - in-degree = out-degree = E - undirected graph - ex. - ![image](https://hackmd.io/_uploads/BJQYbUIGR.png) - degree 的總和 / 2 = E - 練習 1. Tree is connected undirected graph - T 3. A graph is connected, and V = n, 則最小的邊數量為 n-1 - T 4. 有一 undirected graph, and V = n, 若 E >= n, 則此圖必 connected - F - ![image](https://hackmd.io/_uploads/BJZTH8UGR.png) ### 表示方式 1. adjacency matrix - 定義 - V = n, 宣告一二維陣列 A[n][n] - 若 (vi, vj) 存在,則 A[i][j] = 1, 否則 A[i][j] = 0 - undirected graph - ex. - ![image](https://hackmd.io/_uploads/Hy82OLLf0.png) - digraph - ex. - ![image](https://hackmd.io/_uploads/SkMRKIIfA.png) 2. adjacency list - 定義 - V = n, 用 n 條 linked list 表示 graph - linked list node structure | Vertex | link | | -------- | -------- | - undirected graph - ![image](https://hackmd.io/_uploads/S1TxNFkX0.png) - directed graph - ![image](https://hackmd.io/_uploads/rk3xrFkXC.png) - 比較 - 求 graph 的邊的數量 - adjacency list : O(e) - adjacency matrix : O(n^2) - 找某個邊在 graph 中是否存在 - adjacency list : O(e) - adjacency matrix : O(1) - 適用的情境 - adjaceny list : 當邊的數量比 vertex 數量少或差不多,ex. tree - adjacency matrix : 當邊的數量比 vertex 數量多很多,ex. complete graph ### traversal 1. DFS (Depth First Search) - 定義 - 深度優先走訪 - 採用 stack - 步驟 1. 選擇一個 node 為起點 2. 若 node 有分支,則往 node 的 child 走訪;若無分支,則回到 node 的上個 node,回到第一步 3. 持續做 1, 2,直到沒有未走訪過的 node 可以走訪 - ex. - ![image](https://hackmd.io/_uploads/SkdAhKyXR.png) - 演算法 ```java= void dfs(int v) { visited[v] = true; // visited = 存每個 node 是否已經被走訪 int[] adj = getAdj(v); // adj = v node 的所有相鄰 node 的 array for (int i = 0;i < adj.length;i++) { if (!visited[adj[i]]) { dfs(adj[i]); } } } ``` 2. BFS (Breadth First Search) - 定義 - 廣度優先走訪 - 採用 queue - 步驟 1. 選擇一個 node 為起點,放入 queue 2. 將 node 從 queue 中刪除 3. 把 node 還未走訪過的 child,放入 queue 4. 將 queue 裡面的 node 重複做 1, 2, 3 - ex. - ![image](https://hackmd.io/_uploads/HJIcRt1X0.png) - 演算法 ```java= void bfs(int v) { // start from node v int visited[v] = true; // visited = 存每個 node 是否已經被走訪 queue q = new queue(); q.enqueue(v); while (!q.empty()) { v = q.dequeue(); int[] adj = getAdj(v); // adj = v node 的所有相鄰 node 的 array for (int i = 0;i < adj.length;i++) { if (!visited[adj[i]]) { q.enqueue(adj[i]); visited[adj[i]] = true; } } } } ``` - level order traversal - use bfs on binary tree ### spanning tree - 定義 - 沒有 cycle - 包含 graph 的所有頂點,且為 connected - ex. - ![image](https://hackmd.io/_uploads/HymxC2dQR.png) - 其中一棵 spanning tree - ![image](https://hackmd.io/_uploads/B1tZ02OXR.png) - 求法 - dfs - bfs - minimum cost spanning tree - 定義 - 有一 connected graph,且 edge 上有加權成本 - 要找出成本總和最小的 spanning tree - 求法 1. kruskal's - 步驟 1. 挑出 graph 中最小成本邊 2. 若加入此邊不會產生 cycle,則將此邊加入 spanning tree 3. 重複 1,2,直到 spanning tree 有 n-1 個邊 - ex. - ![image](https://hackmd.io/_uploads/SJgzbpOmA.png) - ![image](https://hackmd.io/_uploads/r1o7-TdQC.png) - time complexity - O(eloge) 3. prim's - 步驟 1. 有一空集合 U,用於儲存已挑選的 node 2. 隨機選一個 node 放入 U 3. 找出所有由已挑選和未挑選的 node 組成的邊,選出成本最小的邊 4. 將此邊的未挑選的 node 放入 U,並將其從已挑選的集合刪除 5. 重複 3,4,直到所有 node 都被放入 U - ex. - ![image](https://hackmd.io/_uploads/SJgzbpOmA.png) - ![image](https://hackmd.io/_uploads/HJdYBau7R.png) - time complexity - O(n^2) 5. sollion's - 步驟 1. 將所有的 node 視為一棵 tree 2. 找出每棵 tree 的 min cost edge,且需檢查是否造成 cycle 3. 重複 2,直到剩下一棵 tree or 無邊可挑 - ex. - ![image](https://hackmd.io/_uploads/S1vD56_mC.png) - 比較 - kruskal's 適合 edge 少的 graph(ex. tree) - prim's 適合 edge 多的 graph(ex. complete graph) ### shortest path problem - one to all - 定義 - 在一 digraph 中,求某一個 vertex 到 graph 中其他剩餘的 vertex 的各個最短路徑的距離 1. Dijkstra's - 步驟 1. 宣告一二維陣列 cost[n][n],存相鄰成本 2. 宣告一一維陣列 used[n],存已經走訪過的頂點 3. 宣告一一維陣列 min_dist[n],存起點 vertex 到其他 vertex 的最短路徑的距離 4. 每回合找出不存在於 used 中,且是 min_dist 最小值的 vertex 5. 確認是否可從選擇的 vertex X 到其他 vertex Y 的路徑成本小於目前 min_dist 的值,若有且 vertex Y 不在 used,就換掉 6. 總共做 n-1 回合 - ex. - ![image](https://hackmd.io/_uploads/r1e9EWMN0.png) - note - 不能有負邊 3. Bellman Ford - 步驟 1. 宣告一二維陣列 cost[n][n],存相鄰成本 2. 宣告一一維陣列 min_dist[n],存該 vertex 到其他 vertex 的最短路徑的距離 3. 每一回合 X,就檢查從起點走 X 條路的路徑成本是否有小於 min_dist,若有就換掉 4. 共做 n-1 回合 - ex. - ![image](https://hackmd.io/_uploads/ryTWFWMNR.png) - 紅色邊代表加入該邊會造成負循環 - note - 不能有負循環 - all to all 1. Floyd Warshall - 步驟 1. 宣告一二維陣列 cost[n][n],存相鄰成本 2. 宣告一二維陣列 min_dist[n][n],預設的值等於 cost 的值,存所有 vertex 到其他 vertex 的最短路徑的距離 3. 每回合 X,檢查是否從經過 vertex X 的路徑有小於目前 min_dist 的 value,若有就換掉 4. 共做 n 回合 - ex. - ![image](https://hackmd.io/_uploads/SyvhC-z4C.png) - note - 不能有負循環 ## 問題 - 請同學有問題可以統一留在這邊,上課一起討論