rytlebsk
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.

      Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

      Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

      Explore these features while you wait
      Complete general settings
      Bookmark and like published notes
      Write a few more notes
      Complete general settings
      Write a few more notes
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.

    Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Explore these features while you wait
    Complete general settings
    Bookmark and like published notes
    Write a few more notes
    Complete general settings
    Write a few more notes
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # 演算法 Algorithm **演算法是一個有限的指令集,用以完成指定的工作。** ## 定義: 1. **Input(明確輸入):** 零個或多個輸入。 2. **Output(輸出):** 至少產生一個輸出。 3. **Definiteness(明確性):** 每一步驟必須是明確的,不能含糊不清。 4. **Finiteness(有限性):** 演算法必須在有限的步驟內完成。 5. **Effectiveness(有效性):** 每一步驟都必須是基本的,可以被執行的。 - 演算法(Algorithm) + 資料結構(Data Structure) = 程式(Program) - 複雜的演算法通常被分割為細小的模組(Module),以便於理解與維護。 # 遞迴 Recursion **遞迴是一種在函式內部呼叫自己來解決問題的方法。遞迴機制非常強大,因為它可以清楚的表達複雜的問題,並且通常能夠簡化程式碼。** ## 三種遞迴 1. **直接遞迴 (Direct Recursion):** 函式直接呼叫自己。 2. **間接遞迴 (Indirect Recursion):** 函式 A 呼叫函式 B,函式 B 再呼叫函式 A。 3. **尾遞迴 (Tail Recursion):** 遞迴呼叫是函式的最後一個動作,可以被優化以節省記憶體。 ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image.png) ## 與非遞迴函數的比較 | 特性 | 遞迴函數(Recursive Function) | 非遞迴函數(Non-Recursive Function) | | -------- | ---------------------------- | ---------------------------------- | | 可讀性 | 通常較容易 | 可能較不易 | | 簡潔性 | 通常較緊湊、簡潔 | 可能較複雜、冗長 | | 執行效率 | 可能較低(因為函式呼叫開銷) | 通常較高 | # 空間複雜度(Space Complexity) **空間複雜度是指演算法在執行過程中所需的記憶體空間量,通常以輸入資料的大小來表示。** **空間複雜度通常被區分為下列兩部分:** 1. **固定部分 (Fixed Part):** 指令本身的空間,例如簡單的變數、常數等。 2. **變動部分 (Variable Part):** 資料所需的空間(遞迴的呼叫堆疊之類的)。 根據一個程式 P 的空間需求 S(p),可以表示為: ``` S(p) = c + Sp ``` 其中 c 為固定部分,Sp 為變動部分;而我們通常關注的是 Sp,因為它會隨著輸入資料的大小而改變。 # 時間複雜度 **時間複雜度是指演算法在執行過程中所需的時間量,通常以輸入資料的大小來表示。** 對於一個程式 P 的時間需求 T(p),可以表示為: ``` T(p) = c + Tp ``` 其中 c 是編譯時間(compile time),而 Tp 則是運行時間(run|execution time);我們通常關注的是 Tp,因為它會隨著輸入資料的大小而改變。 有兩種方式可以勘定時間複雜度: 1. **測量(Measurement):** 透過實際執行程式並測量其運行時間來估算時間複雜度(執行程式並記錄 CPU 時間)。 2. **分析(Analysis):** 計算程式步驟或是指令的數量。 ## 時間複雜度的表示法 我們希望能用一些符號表示一個帶有 n 輸入的演算法函數 f(n)的時間或空間複雜度,接下來會介紹一些符號表示不精確但有意義的陳述。 ## Big-Oh (O(n)): 定義: ``` O(g(n)) = { f(n) | 若且唯若「存在」正的常數c和n0,使得對所有n >= n0,有0 <= f(n) <= c*g(n) } ``` **對於當 n >= n0 的所有 n,在 f(n) = O(g(n)) 中,c\*g(n) 是 f(n) 的一個上界。** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-1.png) ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-2.png) - 為使描述 f(n) = O(g(n)) 有意義,應選擇一個盡可能小的 g(n)。 - 常見的時間複雜度類型: |名字|符號| |---|---| |常數時間(constant)| O(1)| |對數時間(logarithmic)| O(log n)| |線性時間(linear)| O(n)| |線性對數時間(linearithmic)| O(n log n)| |平方時間(quadratic)| O(n^2)| |指數時間(exponential)| O(2^n)| |立方時間(cubic)| O(n^3)| |階乘時間(factorial)| O(n!)| |多項式時間(polynomial)| O(n^k) (k 為常數)| |指數時間(exponential)| O(k^n) (k 為常數)| |超指數時間(super-exponential)| O(n^n)| 排序如下: ``` O(1) < O(log n) < O(n) < O(n log n) < O(n^2) < O(n^3) < O(n^k) < O(2^n) < O(k^n) < O(n!) < O(n^n) ``` ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-3.png) ## Omega (Ω(n)) 定義: ``` Ω(g(n)) = { f(n) | 若且唯若「存在」正的常數c和n0,使得對所有n >= n0,有0 <= c*g(n) <= f(n) } ``` **對於當 n >= n0 的所有 n,在 f(n) = Ω(g(n)) 中,c\*g(n) 是 f(n) 的一個下界。** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-4.png) ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-5.png) - 為使描述 f(n) = Ω(g(n)) 有意義,應選擇一個盡可能大的 g(n)。 ## Theta (Θ(n)) 定義: ``` Θ(g(n)) = { f(n) | 若且唯若「存在」正的常數c1、c2和n0,使得對所有n >= n0,有0 <= c1*g(n) <= f(n) <= c2*g(n) } ``` **是一個比 Big-Oh 和 Omega 更加精確的表示法,c\*g(n) 同時是 f(n) 的上界和下界。** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-6.png) ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-7.png) ## 其他的表示法 1. 小 o 符號 (little o notation): 定義: ``` o(g(n)) = { f(n) | 對「所有」正的常數c,存在一個常數n0,使得對所有n >= n0,有0 <= f(n) < c*g(n) } ``` **表示 f(n) 增長速度比 g(n) 慢得多。** 跟 Big-Oh 的差別在於,Big-Oh 允許 f(n) 和 g(n) 增長速度相同,而小 o 則不允許。 2. 小 ω 符號 (little omega notation): 定義: ``` ω(g(n)) = { f(n) | 對「所有」正的常數c,存在一個常數n0,使得對所有n >= n0,有0 <= c*g(n) < f(n) } ``` **表示 f(n) 增長速度比 g(n) 快得多。** 跟 Omega 的差別在於,Omega 允許 f(n) 和 g(n) 增長速度相同,而小 ω 則不允許。 ## 一些轉換(可能不用記...嗎?) 1. 若 f(n) = Θ(g(n)) 且 g(n) = Θ(h(n)),則 f(n) = Θ(h(n))。 2. 若 f(n) = O(g(n)) 且 g(n) = O(h(n)),則 f(n) = O(h(n))。 3. 若 f(n) = Ω(g(n)) 且 g(n) = Ω(h(n)),則 f(n) = Ω(h(n))。 4. 若 f(n) = o(g(n)) 且 g(n) = o(h(n)),則 f(n) = o(h(n))。 5. 若 f(n) = ω(g(n)) 且 g(n) = ω(h(n)),則 f(n) = ω(h(n))。 6. f(n) = Θ(g(n)) 若且唯若 g(n) = Θ(f(n))。 7. f(n) = O(g(n)) 若且唯若 g(n) = Ω(f(n))。 8. f(n) = o(g(n)) 若且唯若 g(n) = ω(f(n))。 9. f(n) = Θ(f(n))。 10. f(n) = O(f(n))。 11. f(n) = Ω(f(n))。 # 大師法則 (Master Method) **大師法則是一種用來分析分治演算法時間複雜度的工具。** ## 大師法則的形式 對於一個遞迴關係式: ``` T(n) = aT(n/b) + f(n) ``` 其中: - a >= 1 是子問題的數量。 - b > 1 是每個子問題的規模縮小比例。 - f(n) 是合併子問題結果所需的時間。 ## 大師法則的三種情況 1. **情況 1:** 如果存在一個常數 ε > 0,使得: ``` f(n) = O(n^(log_b(a) - ε)) ``` 那麼: ``` T(n) = Θ(n^(log_b(a))) ``` 2. **情況 2:** 如果存在一個常數 k >= 0,使得: ``` f(n) = Θ(n^(log_b(a)) * log^k(n)) ``` 那麼: ``` T(n) = Θ(n^(log_b(a)) * log^(k+1)(n)) ``` 3. **情況 3:** 如果存在一個常數 ε > 0,使得: ``` f(n) = Ω(n^(log_b(a) + ε)) ``` 且如果存在一個常數 c < 1,使得對所有足夠大的 n,有: ``` a * f(n/b) <= c * f(n) ``` 那麼: ``` T(n) = Θ(f(n)) ``` # 陣列 Array **陣列是含有多個鍵值對(pairs <index, value>)的資料結構,並使每個索引(index)都對到一個唯一的值(value)。** ## 2D 陣列 - **2D 陣列是陣列的陣列,又被稱為矩陣。** - **一般由 m 個行(row)和 n 個列(column)組成,若行列數相等則為方陣(square matrix)。** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-8.png) - **對於矩陣 A,元素 A~i,j~ = A[i-1][j-1]** - **矩陣能以兩種方法表示:** ## 1. 行主序(row-major order): - **對於矩陣 A,行主序會將 A~1,1~、A~1,2~、...、A~1,n~ 依序存放,接著是 A~2,1~、A~2,2~、...、A~2,n~,以此類推。** ``` (3x3 matrix) [27,31, -4, 5, 8, 16, => [27,31,-4,5,8,16,7,10,15] 7,10, 15] (A1,1,A1,2,A1,3,A2,1,A2,2,A2,3,A3,1,A3,2,A3,3) ``` - **對於 m x n 的矩陣 A,元素 Ai,j 在一維陣列中的位置為:** ``` ls + [(i - 1) * n + (j - 1)] * d ``` 其中 ls 為陣列的起始位置,d 為每個元素的大小。 ## 2. 列主序(column-major order): - **對於矩陣 A,列主序會將 A1,1、A2,1、...、Am,1 依序存放,接著是 A1,2、A2,2、...、Am,2,以此類推。** ``` (3x3 matrix) [27,31, -4, 5, 8, 16, => [27,5,7,31,8,10,-4,16,15] 7,10, 15] (A1,1,A2,1,A3,1,A1,2,A2,2,A3,2,A1,3,A2,3,A3,3) ``` - **對於 m x n 的矩陣 A,元素 A~i,j~ 在一維陣列中的位置為:** ``` ls + [(j - 1) * m + (i - 1)] * d ``` 其中 ls 為陣列的起始位置,d 為每個元素的大小。 ## 三角矩陣 ### 1. 下三角矩陣 (Lower Triangular Matrix) - **一個方陣(Square Matrix)A 的第 i 行最大的非零項目數是 i,且當 i < j 時,A~i,j~ = 0,這樣的矩陣稱為下三角矩陣。** - **下三角矩陣的非零項目數為:** ``` n(n + 1) / 2 ``` ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-9.png) - ## 位置表示 - ## 行主序(row-major order): **對於 n x n 的下三角矩陣 A,元素 A~i,j~ 在一維陣列中的位置為:** ``` ∀i ≥ j, Location(Ai,j) = ls + [(i * (i - 1)) / 2 + j - 1] * d ``` 其中 ls 為陣列的起始位置,d 為每個元素的大小。 - ## 列主序(column-major order): **對於 n x n 的下三角矩陣 A,元素 A~i,j~ 在一維陣列中的位置為:** ``` ∀i ≥ j, Location(Ai,j) = ls + [((2n - j)(j - 1)) / 2 + i - 1] * d ``` 其中 ls 為陣列的起始位置,d 為每個元素的大小。 ### 2. 上三角矩陣 (Upper Triangular Matrix) - **一個方陣(Square Matrix)A 的第 i 行最小的非零項目數是 i,且當 i > j 時,Ai,j = 0,這樣的矩陣稱為上三角矩陣。** - **上三角矩陣的非零項目數為:** ``` n(n + 1) / 2 ``` ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-10.png) - ## 位置表示 - ## 行主序(row-major order): **對於 n x n 的上三角矩陣 A,元素 A~i,j~ 在一維陣列中的位置為:** ``` ∀i ≤ j, Location(Ai,j) = ls + [((2n - i)(i - 1)) / 2 + j - 1] * d ``` 其中 ls 為陣列的起始位置,d 為每個元素的大小。 - ## 列主序(column-major order): **對於 n x n 的上三角矩陣 A,元素 A~i,j~ 在一維陣列中的位置為:** ``` ∀i ≤ j, Location(Ai,j) = ls + [(j * (j - 1)) / 2 + i - 1] * d ``` 其中 ls 為陣列的起始位置,d 為每個元素的大小。 # 鏈結串列 Linked List **鏈結串列是一種動態資料結構,由一系列節點(node)組成,每個節點包含一個或多個資料和指向下一個節點的指標(pointer)。** ## 單向鏈結串列 (Singly Linked List) - **每個節點包含兩個部分: 資料(data)和指向下一個節點的指標(next pointer)。** - **最後一個節點的 next 指標指向 null,表示鏈結串列的結束。** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-11.png) ## 環狀鏈結串列 (Circular Linked List) - **最後一個節點的 next 指標指向鏈結串列的第一個節點,形成一個環狀結構。** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-12.png) ## 雙向鏈結串列 (Doubly Linked List) - **每個節點包含三個部分: 資料(data)、指向下一個節點的指標(next pointer)和指向前一個節點的指標(prev pointer)。** - **這種結構允許雙向遍歷鏈結串列。** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-13.png) ## 雙向環狀鏈結串列 (Doubly Circular Linked List) - **最後一個節點的 next 指標指向鏈結串列的第一個節點,而第一個節點的 prev 指標指向最後一個節點,形成一個雙向環狀結構。** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-14.png) ## 其與陣列之比較 | 特性 | 陣列(Array) | 鏈結串列(Linked List) | | -------------- | ---------------------- | ------------------------ | | 記憶體配置 | 靜態配置,大小固定 | 動態配置,大小可變 | | 連續性 | 元素連續存放 | 元素不一定連續存放 | | 存取方式 | 透過索引存取 | 需從頭節點遍歷 | | 存取時間 | O(1) (透過索引) | O(n) (需遍歷) | | 插入/刪除時間 | O(n) (需移動元素) | O(1) (只需更新指標) | | 記憶體使用效率 | 可能浪費空間(預留空間) | 節點需額外指標空間 | | 資料局部性 | 較好,適合快取 | 較差,可能導致快取未命中 | # 堆疊 Stack **堆疊是一種後進先出(Last In First Out, LIFO)的資料結構,允許在一端進行插入(push)和刪除(pop)操作。** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-19.png) ## 以鏈結串列實作堆疊 - **若資料大小無法確定,則能以鏈結串列實作堆疊。** - **堆疊的頂端(top)指向鏈結串列的第一個節點。** - ## 操作 1. **Push(插入):** - **建立一個新節點,將資料存入,並將其 next 指標指向目前的頂端節點,然後更新頂端指標指向新節點。** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-15.png) 2. **Pop(刪除):** - **檢查堆疊是否為空,若不為空,則將頂端指標指向下一個節點,並釋放原頂端節點的記憶體。** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-16.png) 3. **Peek(查看頂端元素):** - **返回頂端節點的資料,但不刪除該節點。** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-17.png) ## 堆疊排列 Stack Permutation - **給定一個輸入序列,堆疊排列是指透過堆疊操作所能產生的所有可能輸出序列。** - **例如,對於輸入序列 [1, 2, 3],可能的堆疊排列包括:** ``` [1, 2, 3] => push 1, pop 1, push 2, pop 2, push 3, pop 3 [1, 3, 2] => push 1, pop 1, push 2, push 3, pop 3, pop 2 [2, 1, 3] => push 1, push 2, pop 2, pop 1, push 3, pop 3 [2, 3, 1] => push 1, push 2, pop 2, push 3, pop 3, pop 1 [3, 2, 1] => push 1, push 2, push 3, pop 3, pop 2, pop 1 ``` - **並非所有排列都能透過堆疊操作產生,例如 [3, 1, 2] 無法透過堆疊操作達成。** - ## 卡塔蘭數 (Catalan Number) - **卡塔蘭數是一系列自然數,常用於計算堆疊排列的數量。第 n 個卡塔蘭數 C(n) 定義為:** ``` C(n) = (1 / (n + 1)) * (2n choose n) = (2n)! / ((n + 1)! * n!) ``` ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-18.png) - **對於 n 個元素的輸入序列,可能的堆疊排列數量為第 n 個卡塔蘭數 C(n)。** # 佇列 Queue **佇列是一種先進先出(First In First Out, FIFO)的資料結構,允許在一端(rear)進行插入操作,在另一端(front)進行刪除操作。** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-20.png) ## 以陣列實作佇列 - **使用固定大小的陣列來存放佇列元素,並使用兩個指標(front 和 rear)來追蹤佇列的前端和後端位置。** - **當 rear 到達陣列末端時,可以將其重新指向陣列的起始位置,形成環狀佇列(circular queue)。** - ## 操作 0. **宣告** ```c++ #define MAX 10 //改變此值將會改變陣列大小 int queue[MAX]; int front = -1, rear = -1; void insert(void); int delete_element(void); void display(void); ``` 1. **Insert(插入):** - **將新元素加入到 rear 指標所指的位置,然後更新 rear 指標指向下一個位置。** ```c++ void insert() { int num; printf("Enter a number to insert: "); scanf("%d", &num); if(rear == MAX-1) printf("\n OVERFLOW"); else if(front == -1 && rear == -1)front = rear = 0; else { rear++; queue[rear] = num; } } ``` 2. **Delete(刪除):** - **從 front 指標所指的位置移除元素,然後更新 front 指標指向下一個位置。** ```c++ int delete_element() { if(front == -1){ printf("\n UNDERFLOW"); return -1; } else { val = queue[front]; front++; if(front > rear) front = rear = -1; return val; } } ``` 3. **Display(查看前端元素):** - **返回 front 指標所指的元素,但不刪除該元素。** ```c++ void display() { int i; printf("\n"); if(front == -1) printf("Queue is empty"); else { for(i = front; i <= rear; i++) { printf("\t %d", queue[i]); } } } ``` ## 以鏈結串列實作佇列 - **比起使用陣列,鏈結串列在插入和刪除操作上更具彈性,無需考慮容量限制。** - ## 操作 0. **Declare(宣告)** ```c++ #include <stdio.h> #include <conio.h> #include <malloc.h> struct node { int data; struct node *next; }; struct queue { struct node *front; struct node *rear; }; struct queue *q; void create_queue(struct queue *); struct queue *insert(struct queue *, int); struct queue *delete_element(struct queue *); ``` 1. **Create(建立):** - **初始化佇列,將 front 和 rear 指標設為 null。** ```c++ void create_queue(struct queue *q) { q->front = NULL; q->rear = NULL; } ``` 2. **Insert(插入):** - **建立一個新節點,將資料存入,並將其加入到佇列的 rear 位置,然後更新 rear 指標。** ```c++ struct queue *insert(struct queue *q, int value) { struct node *new_node; new_node = (struct node *)malloc(sizeof(struct node)); new_node->data = value; if(q->front == NULL) { q->front = new_node; q->rear = new_node; q->front->next = NULL; q->rear->next = NULL; } else { q->rear->next = new_node; q->rear = new_node; q->rear->next = NULL; } return q; } ``` 3. **Delete(刪除):** - **從佇列的 front 位置移除元素,然後更新 front 指標。** ```c++ struct queue *delete_element(struct queue *q) { struct node *temp; temp = q->front; if(q->front == NULL) { printf("\n UNDERFLOW"); } else { q->front = q->front->next; printf("\n Deleted element is %d", temp->data); free(temp); } return q; } ``` ## 各種變體 1. ## 環形佇列 (Circular Queue) - **環形佇列是一種特殊的佇列,當 rear 指標到達陣列末端時,會重新指向陣列的起始位置,形成一個環狀結構。** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-25.png) - **無法對已滿的佇列進行插入,即便刪除了元素,其已經不具備可被插入的性質。** 2. ## 雙端佇列 (Deque - Double-Ended Queue) - **雙端佇列允許在兩端進行插入和刪除操作,提供更大的靈活性。** - **具有兩個指標,RIGHT 以及 LEFT,任何插入刪除行為無法在中間進行。** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-24.png) - ## 輸入限制雙端佇列 (Input-Restricted Deque) **只能在一端進行插入操作,但可以在兩端進行刪除操作。** - ## 輸出限制雙端佇列 (Output-Restricted Deque) **可以在兩端進行插入操作,但只能在一端進行刪除操作。** 3. ## 優先佇列 (Priority Queue) - **優先佇列是一種特殊的佇列,其中每個元素都有一個優先級,元素被處理的順序取決於其優先級,而非插入順序;具有相同優先級的元素會按照插入順序處理(FCFS, First-Come-First-Served)。** - **廣泛適用於各種操作系統,如煞車優先系統(BOS, Brake Override System)。** - ## 實作方法(Array) - **每個優先級(priority)都有自己的佇列,以及對應的 front 和 rear 指標。** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-21.png) - **插入操作根據元素的優先級將其加入對應的佇列。(以插入元素 R 的優先級為 3 的範例)** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-22.png) - ## 實作方法(鏈結串列) - **每個節點都有三個部分:資料區、優先級區和指向下一個節點的指標。** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-23.png) - **此鏈結串列具以下特點,舉例來說:** 1. **由於 A 具有比 B 更高的優先級(1<2),因此 A 會被插入到 B 的前面。** 2. **C 和 D 具有相同的優先級(2),因此它們依先來後到被排列並被處理。** 3. **優先級以適當將元素排列,我們無法得知低優先級的元素是否比高優先級還要先到。** # 算術表示法 Arithmetic Expression **算術表示法是用來表示數學運算的符號系統,常見的表示法有中序表示法(Infix Notation)、前序表示法(Prefix Notation)和後序表示法(Postfix Notation)。** - **為什麼我們習慣使用中序表示法,卻還需要前序和後序表示法呢?** 對於人類來說,中序表示法較為直觀且易於理解,因為它符合我們日常使用的數學表達方式。然而,對於計算機來說,中序表示法需要額外的規則來處理運算優先級和括號,這使得解析和計算變得複雜。前序和後序表示法則不需要括號,且運算優先級是由符號的位置決定的,因此更適合計算機進行處理。 - **舉例來說:** | 表示法 | 例子 | | ------ | ---------- | | 中序 | A + B \* C | | 前序 | + A \* B C | | 後序 | A B C \* + | - **簡單將中序表示轉換為前/後序表示的描述:** 1. 將表達式完全補上括號 2. 將所有運算子取代其對應的左/右括號 3. 移除所有括號 ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-26.png) ## 將中序轉後序的演算法 1. **建立一個空的堆疊(stack)和一個空的輸出串列(output list)。** 2. **從左到右掃描中序表示法的每個符號:** - **如果符號是運算元(operand),將其加入輸出串列。** - **如果符號是左括號'(',將其推入堆疊。** - **如果符號是右括號')',則從堆疊中彈出運算子並加入輸出串列,直到遇到左括號為止,並取出左括號。** - **如果符號是運算子(operator),則從堆疊中彈出運算子並加入輸出串列,直到堆疊頂端的運算子優先級低於當前運算子,然後將當前運算子推入堆疊。** **運算子優先級如下:** |優先級|運算子| |---|---| |1| -(一元負號)| |2| not| |3| \*. /, %, &| |4| +, -, \^(xor), \| | |5| <, <=, >, >=, =, != | 3. **掃描結束後,將堆疊中剩餘的運算子全部彈出並加入輸出串列。** 4. **輸出串列即為後序表示法。** - **舉 (A - (B / C +(D % E \* F) / G) \* H) 為例:** | 步驟 | 符號 | 堆疊(Stack) | 輸出串列(Output List) | | ---- | ---- | ------------------ | -------------------------- | | 1 | \( | \( | | | 2 | A | \( | A | | 3 | - | \(- | A | | 4 | \( | \(-\( | A | | 5 | B | \(-\( | AB | | 6 | / | \(-\(/ | AB | | 7 | C | \(-\(/ | ABC | | 9 | + | \(-\(+ | ABC/ | | 10 | \( | \(-\(+\( | ABC/ | | 11 | D | \(-\(+\( | ABC/D | | 12 | % | \(-\(+\(% | ABC/D | | 13 | E | \(-\(+\(% | ABC/DE | | 14 | \* | \(-\(+\(\* | ABC/DE% | | 15 | F | \(-\(+\(\* | ABC/DE%F | | 16 | \) | \(-\(+ | ABC/DE%F\* | | 17 | / | \(-\(+/ | ABC/DE%F\* | | 18 | G | \(-\(+/ | ABC/DE%F\*G | | 19 | \) | \(- | ABC/DE%F\*G/+ | | 20 | \* | \(-\* | ABC/DE%F\*G/+ | | 21 | H | \(-\* | ABC/DE%F\*G/+H | | 22 | \) | | ABC/DE%F\*G/+H\*- | ## 計算後序表示法的值 1. **建立一個空的堆疊(stack)。** 2. **從左到右掃描後序表示法的每個符號:** - **如果符號是運算元(operand),將其推入堆疊。** - **如果符號是運算子(operator),則從堆疊中彈出兩個運算元,根據運算子進行計算 B op A(A 是最上面彈出的運算元,B 是 A 下面的的運算元),然後將結果推入堆疊。** 3. **掃描結束後,堆疊中唯一的元素即為後序表示法的計算結果。** - **舉 934\*8+4/- 為例:** | 步驟 | 符號 | 堆疊(Stack) | | ---- | ---- | ------------------ | | 1 | 9 | 9 | | 2 | 3 | 9, 3 | | 3 | 4 | 9, 3, 4 | | 4 | \* | 9, 12 | | 5 | 8 | 9, 12, 8 | | 6 | + | 9, 20 | | 7 | 4 | 9, 20, 4 | | 8 | / | 9, 5 | | 9 | - | 4 | # 樹 Tree **樹是一種非線性(non-linear)資料結構,由節點(node)組成,節點之間透過邊(edge)連接,形成層次(hierarchical)結構。** - **對於下圖的樹:** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-27.png) - 節點 B, C, D 是節點 A 的子節點(children)。 - 節點 A 是節點 B, C, D 的父節點(parent)。 - **根節點(root node):** 代表整個樹的頂端節點,在此例中為節點 A。 - **葉/終端節點(leaf/ terminal node):** 沒有子節點的節點,在此例中為節點 E, F, J, K, H, I。 - **子樹(subtree):** 由某節點及其所有後代節點所組成的樹,如 T1, T2, T3。 - **路徑(path):** 由一連串節點所組成的序列,從一個節點到另一個節點的連接。如: 從節點 A 到節點 I 的路徑為 `A -> D -> I`。 - **祖輩節點(ancestor node):** 某節點的所有上層節點,從該節點一直到根節點的路徑上的節點。如: 節點 I 的祖先節點為 D 和 A。 - **孫輩節點(descendant node):** 某節點的所有下層節點,從該節點一直到葉節點的路徑上的節點。如: 節點 E 和 F 是節點 B 的孫輩節點。 - **層數(level number):** 節點所在的層次,根節點的層數為 0,其子節點的層數為 1,依此類推。如:節點 H 和 I 的層數為 2。 - **度(degree):** 節點的子節點數量。如: 節點 A 的度為 3,節點 B 的度為 2,節點 E 的度為 0。 ## 二元樹 Binary Tree **二元樹是一種特殊的樹結構,其中每個節點最多有 0, 1, 2 個子節點,分別稱為左子節點(left child)和右子節點(right child)。** - **手足節點(sibling node):** 具有相同階層及父節點的節點稱為手足節點。 - **高度(height):** 節點到其最遠葉節點的節點總數;一棵高度為 h 的二元樹最少有 2^h - 1 個節點。 - **形似(similar):** 兩棵二元樹若其結構相同,則稱為形似的二元樹。 - **複製(copies):** 將一棵二元樹形似且資料一致於另一棵二元樹,稱為複製。 - ## 將通用樹(General Tree)轉換為二元樹(Binary Tree) **將通用樹的每個節點的第一個子節點作為二元樹的左子節點,其他子節點則依序作為右子節點。** **例如,通用樹中的節點 A 有三個子節點 B, C, D,在轉換後的二元樹中,B 成為 A 的左子節點,C 成為 B 的右子節點,D 成為 C 的右子節點。** - ## 實作(Array) - **使用陣列來表示二元樹,對於節點在陣列中的位置 i,其左子節點的位置為 2i + 1,右子節點的位置為 2i + 2,父節點的位置為 (i - 1) / 2。** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-28.png) - ## 實作(Linked List) - **使用節點結構來表示二元樹,每個節點包含資料(data)、指向左子節點的指標(left pointer)和指向右子節點的指標(right pointer)。** ```c++ struct node { int data; struct node *left; struct node *right; }; ``` ## 完全二元樹 Complete Binary Tree **完全二元樹是一種特殊的二元樹,除了最後一層外,每一層的節點數量都達到最大值,且最後一層的節點從左至右依序填滿。** - **例如,以下的二元樹為完全二元樹:** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-29.png) - **而以下的二元樹則不是完全二元樹,因為最後一層的節點沒有從左至右依序填滿:** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-30.png) ## 延展二元樹 Extended Binary Tree - **延展二元樹是一種特殊的二元樹,其中每個非葉節點都有兩個子節點,葉節點則沒有子節點。** - **為了達成這個目的,延展二元樹會在原本的葉節點位置插入額外的空節點(null node),使得每個非葉節點都有兩個子節點。** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-31.png) ## 競賽/選擇樹 Tournament/Selection Tree - **競賽樹是一種特殊的二元樹,用於模擬比賽過程,其中每個非葉節點代表一場比賽的勝者,葉節點則代表參賽者。** - **在競賽樹中,葉節點通常存放參賽者的資料,而非葉節點則存放比賽結果,如勝者的資料或分數。** ## 二元樹的遍歷 Binary Tree Traversal **二元樹的遍歷是指按照特定順序訪問二元樹中的每個節點。常見的遍歷方法有前序遍歷(preorder traversal)、中序遍歷(inorder traversal)和後序遍歷(postorder traversal)。** - **前序遍歷(Preorder Traversal):** 先訪問根節點,然後遞迴訪問左子樹,最後遞迴訪問右子樹。 - **中序遍歷(Inorder Traversal):** 先遞迴訪問左子樹,然後訪問根節點,最後遞迴訪問右子樹。 - **後序遍歷(Postorder Traversal):** 先遞迴訪問左子樹,然後遞迴訪問右子樹,最後訪問根節點。 - **階序遍歷(Level-order Traversal):** 按照層次從上到下、從左到右依序訪問節點。 - **例如,對於以下的二元樹:** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-32.png) - 前序遍歷結果為: A, B, D, C, E, F, G, H, I - 中序遍歷結果為: B, D, A, E, H, G ,I, F, C - 後序遍歷結果為: D, B, H, I, G, F, E, C, A - 階序遍歷結果為: A, B, C, D, E, F, G, H, I - ### 藉由遍歷構建二元樹 **給定前序和中序遍歷序列,可以唯一確定一棵二元樹,並透過遞迴方法構建該二元樹。** **同理,給定後序和中序遍歷序列也可以唯一確定一棵二元樹,並透過遞迴方法構建該二元樹。** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-34.png) - #### 補充:可定義二元樹的方法(Midterm 2023) 透過任選集合 A 和集合 B,各選一項即可定義二元樹: - 集合 A: 1. 階序 Level-order 2. 前序 Pre-order 3. 後序 Post-order - 集合 B: 1. 二元搜尋樹 Binary Search Tree 2. 完全二元樹 Complete Binary Tree 3. 中序 In-order ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-33.png) ## 引線二元樹 Threaded Binary Tree **引線二元樹是一種特殊的二元樹,利用空指標來建立節點之間的連結,使得在不使用堆疊或遞迴的情況下也能進行遍歷。** **在引線二元樹中,若節點的左子指標為 null,則將其指向該節點在中序遍歷中的前驅(predecessor)節點;若節點的右子指標為 null,則將其指向該節點在中序遍歷中的後繼(successor)節點。** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-35.png) - ## 單路徑引線二元樹(One-way Threaded/Single-Threaded Binary Tree) **每個節點只有一個空指標被用來建立引線,若右側出現引線,則他會指向該節點在中序遍歷中的後繼(successor)節點,此樹稱右引線二元樹(right-threaded binary tree);而若左側出現引線,則他會指向該節點在中序遍歷中的前驅(predecessor)節點,此樹稱左引線二元樹(left-threaded binary tree)。** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-36.png) - ## 雙路徑引線二元樹(Two-way Threaded/Double-Threaded Binary Tree)/完全引線二元樹(Full Threaded Binary Tree) **每個節點的兩個空指標都被用來建立引線,左側的引線指向該節點在中序遍歷中的前驅(predecessor)節點,右側的引線指向該節點在中序遍歷中的後繼(successor)節點。** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-37.png) # 各種重要的二元樹變體 ## 二元搜尋樹 Binary Search Tree **二元搜尋樹是一種特殊的二元樹,其中每個節點的左子樹包含的所有節點值均小於該節點值,右子樹包含的所有節點值均大於該節點值。** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-38.png) ### 搜尋 Search - **從根節點開始,若搜尋值小於當前節點值,則遞迴搜尋左子樹;若搜尋值大於當前節點值,則遞迴搜尋右子樹;若相等則找到該節點。** - **c++ 範例:** ```c++ node *search(node *root, int key) { if(root == NULL) return &nullNode;// A nullNode is a global node indicating not found if (root->data == key) return root; if (key < root->data) return search(root->left, key); return search(root->right, key); } ``` ### 插入 Insert - **從根節點開始,若插入值小於當前節點值,則遞迴插入左子樹;若插入值大於當前節點值,則遞迴插入右子樹;直到找到適當的空位置,將新節點插入。** - **c++ 範例:** ```c++ node* insert(node* root, int key) { if (root == NULL) { return newNode(key); } if (key < root->data) { root->left = insert(root->left, key); } else { root->right = insert(root->right, key); } return root; } ``` ### 刪除 Delete - **從根節點開始,尋找要刪除的節點。若找到該節點,根據其子節點數量進行刪除操作:** 1. **若該節點沒有子節點,直接刪除該節點。** 2. **若該節點有一個子節點,將該節點的父節點指向其唯一的子節點,然後刪除該節點。** 3. **若該節點有兩個子節點,找到該節點右子樹中的最小值節點(或左子樹中的最大值節點),將其值複製到要刪除的節點,然後遞迴刪除該最小值(或最大值)節點。** - **c++ 範例:** ```c++ node* deleteNode(node* root, int key) { if (root == NULL) return root; if (key < root->data) { root->left = deleteNode(root->left, key); } else if (key > root->data) { root->right = deleteNode(root->right, key); } else { // Node with only one child or no child if (root->left == NULL) { node* temp = root->right; free(root); return temp; } else if (root->right == NULL) { node* temp = root->left; free(root); return temp; } // Node with two children: Get the inorder successor (smallest in the right subtree) node* temp = minValueNode(root->right); root->data = temp->data; root->right = deleteNode(root->right, temp->data); } return root; } ``` ## AVL 樹 AVL Tree **AVL 樹是一種自平衡的二元搜尋樹,透過在每個節點維護一個平衡因子(balance factor),並使每個節點的平衡因子為-1、0 或 1 ,以此確保樹的高度保持在 O(log n) 的範圍內。** **一個合法的 AVL 樹有三種情況:** 1. **根節點平衡因子為 0,表示左子樹和右子樹高度相等。(Balanced)** 2. **根節點平衡因子為 1,表示左子樹高度比右子樹高 1。(Left heavy)** 3. **根節點平衡因子為 -1,表示右子樹高度比左子樹高 1。(Right heavy)** ### 搜尋 Search - **與二元搜尋樹的搜尋方法相同,從根節點開始,根據搜尋值與當前節點值的比較結果,遞迴搜尋左子樹或右子樹,直到找到該節點或達到葉節點。** ### 插入 Insert - **與二元搜尋樹的插入方法相同,將新節點插入到適當的位置。** - **插入新節點後,從插入點回溯至根節點,更新每個節點的高度和計算平衡因子。若發現某個節點的平衡因子不在 -1 至 1 範圍內,則進行適當的旋轉操作以恢復平衡。** - **旋轉操作包括:** 1. **雙右臂旋(Right-Right Rotation):** 用於左子樹過高的情況。 - 插入 89 ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-40.png) 插入 89 後,使得根節點的平衡因子為-2,是 Right heavy 的情況,因此需要進行雙右臂旋來恢復平衡。 取根節點的右節點 63 作為新的根節點,並將原本的根節點 45 設為 63 的左子節點,63 的左子節點 54 設為 45 的右子節點。 2. **雙左臂旋(Left-Left Rotation):** 用於右子樹過高的情況。 - 插入 18 ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-39.png) 插入 18 後,使得根節點的平衡因子為 2,是 Left heavy 的情況,因此需要進行雙左臂旋來恢復平衡。 取根節點 36 作為新的根節點,並將原本的根節點 45 設為 36 的右子節點,36 的右子節點 39 設為 45 的左子節點。 3. **左右臂旋(Left-Right Rotation):** 用於左子樹的右子樹過高的情況。 - 插入 37 ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-41.png) 插入 37 後,使得根節點的平衡因子為 2,是 Left heavy 的情況,但根節點 45 的左子節點 36 的平衡因子為-1,是 Right heavy 的情況。 因此需要先對 36 進行右臂旋,再對根節點 45 進行左臂旋來恢復平衡。 4. **右左臂旋(Right-Left Rotation):** 用於右子樹的左子樹過高的情況。 - 插入 60 ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-42.png) 插入 60 後,使得根節點的平衡因子為-2,是 Right heavy 的情況,但根節點 45 的右子節點 63 的平衡因子為 1,是 Left heavy 的情況。 因此需要先對 63 進行左臂旋,再對根節點 45 進行右臂旋來恢復平衡. ### 刪除 Delete - **與二元搜尋樹的刪除方法相同,尋找並刪除指定節點。** - **刪除節點後,從刪除點回溯至根節點,更新每個節點的高度和計算平衡因子。若發現某個節點的平衡因子不在 -1 至 1 範圍內,則進行適當的旋轉操作以恢復平衡。** - **旋轉操作包括:** 1. **R0 旋轉(R0 Rotation):** 用於旋轉後的新根原平衡因子為 0 的情況,包含 RR 或 LL 旋轉。 - 刪除 72 ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-43.png) 刪除 72 後,使得根節點的平衡因子為 2,是 Left heavy 的情況,因此需要進行 R0 旋轉來恢復平衡。 取根節點 45 的左子節點 36 作為新的根節點,並將原本的根節點 45 設為 36 的右子節點,36 的右子節點 39 設為 45 的左子節點。 2. **R1 旋轉(R1 Rotation):** 用於旋轉後的新根原平衡因子為 1 的情況,包含 LL 或 RL 旋轉。 - 刪除 72 ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-45.png) 刪除 72 後,使得根節點的平衡因子為 2,是 Left heavy 的情況,取根節點 45 的左子節點 36 作為新的根節點,而其平衡因子為 1,因此需要進行 R1 旋轉來恢復平衡。 又因為 36 的平衡因子為 1,是 Left heavy 的情況,所以在兩個節點都為 Left heavy 的情況下,直接對根節點 45 進行雙左臂旋來恢復平衡。 3. **R-1 旋轉(R-1 Rotation):** 用於旋轉後的新根原平衡因子為 -1 的情況,包含 RR 或 LR 旋轉。 - 刪除 72 ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-44.png) 刪除 72 後,使得根節點的平衡因子為 2,是 Left heavy 的情況,取根節點 45 的左子節點 36 作為新的根節點,而其平衡因子為-1,因此需要進行 R-1 旋轉來恢復平衡。 又因為 36 的平衡因子為-1,是 Right heavy 的情況,因此需要先對 36 進行右臂旋,再對根節點 45 進行左臂旋來恢復平衡。 ## 2-3 樹 2-3 Tree **2-3 樹是一種自平衡的多路搜尋樹,其中每個節點可以有兩個或三個子節點,並且包含一至兩筆資料。(是一種 Degree = 3 的 B 樹)** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-50.png) ### 搜尋 Search - **與二元搜尋樹的搜尋方法相同,從根節點開始,根據搜尋值與當前節點資料的比較結果,遞迴搜尋適當的子節點,直到找到該資料或達到葉節點。** ### 插入 Insert - **從根節點開始,尋找適當的葉節點位置插入新資料。** - **若葉節點有空位,直接插入新資料。** - **若葉節點已滿(包含兩筆資料),則將該節點分裂為兩個節點,並將中間的資料提升至父節點。** - **若父節點也已滿,則遞迴進行分裂操作,直到達到根節點。** - **若根節點分裂,則創建一個新的根節點,使樹的高度增加一層。** - 插入 37 ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-46.png) 插入 37 後,葉節點 [37, 39, 45] 已滿,因此將其分裂為兩個節點 [37] 和 [45],並將中間的資料 39 提升至父節點。 ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-47.png) ### 刪除 Delete - **從根節點開始,尋找要刪除的資料所在的節點。** - **當刪除的是內部節點時,則需在移除資料後,尋找其後中序表示法的繼節點進行替換。** - **當刪除的是葉節點時,直接刪除該資料。** - **若上述任何行為導致節點資料數量低於最低要求(即 1 筆資料),則需進行合併或借用操作以恢復平衡。** 1. **當一個點為空點時,需要借用其與手足節點(sibling node)夾擠的父節點的資料,並形成一個新的節點。** 2. **當上述行為使得節點數超過最大值(即 2 筆資料),則需進行分裂操作,將中間的資料提升至父節點。** 3. **若有任一節點不合法,則重複上述步驟直到整棵樹合法為止。** - **一個例子** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-48.png) ## 紅黑樹 Red-Black Tree **紅黑樹是一種自平衡的二元搜尋樹,其中每個節點都被標記為紅色或黑色,並且遵循以下規則以確保樹的平衡:** 1. **每個節點要麼是紅色,要麼是黑色。** 2. **根節點必須是黑色。** 3. **所有葉節點(空節點)都是黑色。** 4. **如果一個節點是紅色,則其子節點必須是黑色(即不允許連續的紅色節點)。** 5. **從任一節點到其所有後代葉節點的路徑上,必須包含相同數量的黑色節點。** 6. **從根節點出發的最長路徑不會超過最短路徑的兩倍長度。** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-49.png) ### 搜尋 Search - **與二元搜尋樹的搜尋方法相同,從根節點開始,根據搜尋值與當前節點值的比較結果,遞迴搜尋左子樹或右子樹,直到找到該節點或達到葉節點。** ### 插入 Insert - **與二元搜尋樹的插入方法相同,將新節點插入到適當的位置,並將其標記為紅色。** - **插入新節點後,檢查並修正紅黑樹的規則,可能需要進行重新著色(recoloring)和旋轉(rotation)操作以恢復平衡。** - **重新著色和旋轉操作包括:** - **重新著色(Recoloring):** 若出現有一個黑色節點擁有兩個紅色子節點的情況,則將該黑色節點重新著色為紅色,並將其兩個紅色子節點重新著色為黑色。 - **旋轉(Rotation):** 若出現連續的紅色節點,則根據情況進行左臂旋(left rotation)或右臂旋(right rotation)來恢復平衡。 - **插入一個元素的執行規則:** 1. **搜尋適合的位置用以插入新節點。** 2. **若在搜尋過程中,出現了有兩個紅色子節點的黑色節點:** 1. **將該黑色節點重新著色為紅色,並將其兩個紅色子節點重新著色為黑色。** 2. **尋找是否出現連續的紅色節點,若有則進行旋轉操作以恢復平衡。** 3. **插入欲插入的元素並將其標記為紅色。** 4. **尋找是否出現連續的紅色節點,若有則進行旋轉操作以恢復平衡。** 5. **確保根節點為黑色。** **助記規則:** ` Color Change → Rotation → Insert → Rotation → Check Root` ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-51.png) ### 刪除 Delete **課上未講解刪除部分,故此部分留空。** ### 與 AVL 樹的比較 - **AVL 樹較為嚴格地保持平衡,適合讀取頻繁且寫入較少的應用場景; 紅黑樹不具如此平衡的特性,但其能保證在讀取、寫入、刪除都至少為對數時間複雜度(O(log n))。** - **兩者在相同數量級的時間複雜度下,紅黑樹通常具有較好的插入和刪除性能,因此較為靈活,其被廣泛運用在如 C++ STL 的 map 和 set 容器中。** ## B 樹 B-Tree **B 樹是一種自平衡的多路搜尋樹(multi-way search tree),對於階數(或稱為度, degree)為 m 的 B 樹,可以有 m-1 筆資料和 m 個指向子節點的指標。** **其作為多路搜尋樹的特殊版本,除了具備上述特性,B 樹具有以下額外的特性:** 1. **根節點至少有兩個子節點。** 2. **每個節點最多有 m 個子節點(即最多有 m-1 筆資料)。** 3. **每個非根節點至少有⌈m/2⌉個子節點(即至少有⌈m/2⌉-1 筆資料)。** 4. **所有葉節點都位於同一階層。** - m = 4(最多 4 個、最少 2 個子節點,最多 3 筆、最少 1 筆資料)的 B 樹範例: ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-52.png) ### 搜尋 Search - **與二元搜尋樹的搜尋方法相似,從根節點開始,根據搜尋值與當前節點資料的比較結果,遞迴搜尋適當的子節點,直到找到該資料或達到葉節點。** ### 插入 Insert - **從根節點開始,尋找適當的葉節點位置插入新資料。** - **若葉節點有空位,直接插入新資料並排序。** - **若葉節點已滿(包含 m-1 筆資料),則將該節點分裂為兩個節點,並將中間的資料提升至父節點。(如同 2-3 樹的分裂操作)** - **一個較為複雜,需要多次分裂的插入範例:(m = 5, 最多 5 個、最少 3 個子節點,最多 4 筆、最少 2 筆資料)** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-53.png) ### 刪除 Delete - **依照刪除位置及各節點元素數量的情況有所不同:** | 刪除位置 | 狀態 | 處理方式 | | ---- | ------------- | ------------------ | | 內部節點 | — | 用前驅或後繼替代,再轉為葉節點刪除 | | 葉節點 | 鍵值 > 最小 | 直接刪除 | | 葉節點 | 鍵值 = 最小,左兄弟可借 | 左兄弟最大鍵上推 → 父鍵下拉 | | 葉節點 | 鍵值 = 最小,右兄弟可借 | 右兄弟最小鍵上推 → 父鍵下拉 | | 葉節點 | 左右兄弟都不可借 | 合併節點 → 若父節點不足則向上調整 | - **一個較為複雜,需要多次合併的刪除範例:(m = 5, 最多 5 個、最少 3 個子節點,最多 4 筆、最少 2 筆資料)** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-54.png) ## B+ 樹 B+ Tree **B+ 樹是一種自平衡的多路搜尋樹,是 B 樹的變體,其主要特點包括:** - **所有資料都存放在葉節點(硬碟),內部節點僅用於索引(記憶體)。** - **葉節點之間透過鏈結串列(linked list)相連,便於範圍查詢(range query)和順序存取(sequential access)。** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-55.png) ### 搜尋 Search - **與二元搜尋樹的搜尋方法相同,從根節點開始,根據搜尋值與當前節點資料的比較結果,遞迴搜尋適當的子節點,直到找到該資料或達到葉節點。** ### 插入 Insert - **從根節點開始,尋找適當的葉節點位置插入新資料。** - **若葉節點有空位,直接插入新資料並排序。** - **若葉節點已滿(包含 m-1 筆資料),則將該節點分裂為兩個節點,並將右節點的最小值作為索引複製到父節點** - **若是對內部節點(索引)進行分裂,則不須複製,直接提升。** - **一個較為複雜,需要多次分裂的插入範例:(m = 4, 最多 4 個、最少 2 個子節點,最多 3 筆、最少 1 筆資料)** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-56.png) ### 刪除 Delete - **對於 B+ 樹的刪除操作,主要涉及葉節點的資料刪除,並根據需要進行節點的合併或借用,以維持樹的平衡和結構完整性。** - **刪除葉節點相關操作後可能會遇到兩種情況:** 1. **若葉節點的資料數量低於最低要求(即⌈m/2⌉-1 筆資料),則與兄弟節點進行合併,並刪除其夾擠的索引。** 2. **若內部節點(索引)的資料數量低於最低要求,則與兄弟節點以及他們夾擠的父節點合併組成新的節點。** - **相關操作舉例:(m = 4, 最多 4 個、最少 2 個子節點,最多 3 筆、最少 1 筆資料)** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-57.png) ## 二元堆積 Binary Heap **二元堆積是一種完全二元樹,分為最大堆積(max-heap)和最小堆積(min-heap)兩種形式。** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-58.png) - ### 最大堆積 Max-Heap **在最大堆積中,每個節點的值都大於或等於其子節點的值,根節點擁有整個堆積中的最大值。** - ### 最小堆積 Min-Heap **在最小堆積中,每個節點的值都小於或等於其子節點的值,根節點擁有整個堆積中的最小值。** ### 插入 Insert - **將新元素插入到堆積的最後一個位置,然後依照他是 max 還是 min heap,透過「上浮(up-heap)」操作將其移動到適當的位置,以維持堆積的性質。** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-59.png) ### 刪除 Delete - **二元堆積通常只允許刪除根節點(最大或最小值),刪除後,將最後一個元素移動到根節點位置,然後依照他是 max 還是 min heap,透過「下沉(down-heap)」操作將其移動到適當的位置,以維持堆積的性質。** - 逐漸讓 11 下沉 ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-60.png) ## 伸展樹 Splay Tree **伸展樹是一種自調整的二元搜尋樹,透過在每次訪問節點後進行旋轉操作,將該節點移動到根節點位置,以提高後續訪問的效率。** - **伸展樹的主要旋轉操作有三項:** 1. **Zig 操作(類似 R 或 L rotation):** 當訪問的節點是其父節點的左/右子節點時,進行右/左旋轉。 2. **Zig-Zig 操作(類似 LL 或 RR rotation):** 當訪問的節點和其父節點以及祖輩都是同一邊的節點時,先對父節點進行旋轉,再對訪問的節點進行旋轉。 3. **Zig-Zag 操作(類似 LR 或 RL rotation):** 當訪問的節點是其父節點以及祖輩反邊的子節點,先對訪問的節點進行旋轉,再對訪問節點的父節點進行旋轉。 **透過這些旋轉操作,伸展樹能夠將頻繁訪問的節點移動到樹的頂端,從而提高訪問效率。** ### 搜尋 Search - **與二元搜尋樹的搜尋方法相同,從根節點開始,根據搜尋值與當前節點值的比較結果,遞迴搜尋左子樹或右子樹,直到找到該節點或達到葉節點。** - **找到該節點後,進行伸展操作,將該節點移動到根節點位置。** - **若未找到該節點,則對最後訪問的節點進行伸展操作。** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-61.png) ### 插入 Insert - **與二元搜尋樹的插入方法相同,將新節點插入到適當的位置。** - **插入新節點後,對該節點進行伸展操作,將其移動到根節點位置。** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-62.png) ### 刪除 Delete - **從根節點開始,尋找要刪除的節點。** - **若找到該節點,對其進行伸展操作,將其移動到根節點位置。** - **刪除根節點後,取左子樹最大或是右子樹最小的節點伸展至作為新的根節點,並合併成一棵新的樹。** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-63.png) ### 優劣勢分析 Pros & Cons - **優勢**:其能夠自我調整,將頻繁訪問的節點移動到樹的頂端,提高訪問效率,很適合用在如快取(cache)等應用場景。且相較於其他自平衡樹(如 AVL 樹、紅黑樹),伸展樹的實作較為簡單,無需維護額外的平衡資訊。 - **劣勢**:由於每次訪問都會進行伸展操作,可能導致樹的結構變得不平衡,從而影響其他節點的訪問效率。 ## 霍夫曼樹 Huffman Tree **霍夫曼樹是一種用於進行霍夫曼編碼的數,其核心思想在於將最常用的資料以較短的編碼表示,而較少用的資料以較長的編碼表示,從而達到資料壓縮的目的。** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-64.png) ### 加權外部路徑長度 Weighted External Path Length (WPL) **每個節點的權重(weight)通常是根據該節點所存在的階層作加權。** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-66.png) - **對於 T1 來說,其 WPL = :** `2×3+3×3+5×2+11×3+5×3+2×2=77` ### 建立霍夫曼樹的步驟 Steps to Build Huffman Tree **給定一些帶有權重的節點,透過以下步驟來建立霍夫曼樹:** 1. **將所有節點按照權重從小到大排序,並將其視為獨立的樹。** 2. **從排序後的節點中,選擇兩個權重最小的節點,將它們合併成一個新的節點,該新節點的權重為兩個子節點權重之和。** 3. **將新節點插入到節點集合中,並重新排序。** 4. **重複步驟 2 和 3,直到所有節點都被合併成一棵樹為止。** - 以此類推,最終形成的樹即為霍夫曼樹。 ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-67.png) ### 霍夫曼編碼&解碼 Huffman Coding & Decoding **透過霍夫曼樹,可以為每個節點分配一個唯一的二進位編碼,該編碼是根據從根節點到該節點的路徑來決定的:向左走記為 0,向右走記為 1。** - **編碼 Coding:** - **從根節點開始,根據要編碼的資料,沿著霍夫曼樹的路徑移動,直到到達對應的葉節點,並將沿途的二進位編碼組合起來,即為該資料的霍夫曼編碼。** - **解碼 Decoding:** - **從霍夫曼編碼的起始位置開始,根據二進位編碼,沿著霍夫曼樹的路徑移動,遇到 0 則向左走,遇到 1 則向右走,直到到達葉節點,即為解碼後的資料。** ## 各種變體的比較 | 資料結構 | 平衡性 | 節點結構 | 主要用途 | 優勢 | 劣勢 | 插入時間複雜度 | 刪除時間複雜度 | 搜尋時間複雜度 | | --------------------------- | -------------------------------- | ---------------------------- | -------------------------- | ------------------------------- | ------------------------------ | ------------------------ | ------------------------ | -------------------------------- | | **二元搜尋樹 (BST)** | 不一定平衡 | 每個節點最多 2 個子節點 | 基本搜尋與排序 | 實作簡單、直觀 | 若輸入資料偏序,會退化成鏈 | 平均 O(log n),最差 O(n) | 平均 O(log n),最差 O(n) | 平均 O(log n),最差 O(n) | | **AVL 樹** | 嚴格平衡 (高度差 ≤ 1) | 每節點多存高度與平衡因子 | 需要快速搜尋的場景 | 搜尋效率佳 (接近理想 log n) | 插入刪除較複雜,需要旋轉 | O(log n) | O(log n) | O(log n) | | **2-3 樹** | 完全平衡 | 節點可含 2~3 子樹 | 多路搜尋基礎樹 | 高度穩定,搜尋效率穩定 | 實作複雜 | O(log n) | O(log n) | O(log n) | | **紅黑樹** | 弱平衡(黑高平衡) | 每節點多存顏色 | STL map、set、Java TreeMap | 插入刪除效率高於 AVL(少旋轉) | 搜尋稍慢於 AVL | O(log n) | O(log n) | O(log n) | | **B 樹** | 完全平衡,多路搜尋 | 每節點存多個 key 與子樹指標 | 檔案系統、資料庫索引 | 減少磁碟存取次數 | 實作較複雜 | O(log n) | O(log n) | O(log n) | | **B+ 樹** | 完全平衡,所有資料在葉節點 | 內部節點只存索引,葉節點串接 | 資料庫、檔案系統最常用索引 | 區間搜尋快速、磁碟存取效率高 | 搜尋單一值時需要到葉節點 | O(log n) | O(log n) | O(log n) | | **二元堆積 (Binary Heap)** | 不保證 BST 性質,只保證堆序 | 完全二元樹 | 優先佇列 (Priority Queue) | 取最大/最小值極快 | 無法快速搜尋特定值 | O(log n) | O(log n) | **O(1)** 取極值,搜尋其他值 O(n) | | **伸展樹 (Splay Tree)** | 根據存取頻率自我調整,不嚴格平衡 | 普通 BST 節點 | 熱點資料常被重複查詢 | 近期存取的元素極快 (平攤效率好) | 單次操作最差 O(n) | 均攤 O(log n) | 均攤 O(log n) | 均攤 O(log n) | | **霍夫曼樹 (Huffman Tree)** | 無需平衡,依頻率建滿二元樹 | 每節點存編碼權重 | 資料壓縮 (如 ZIP, JPEG) | 能產生最省空間的前綴碼 | 只能用於編碼,不能用於一般查詢 | 建樹 O(n log n) | 無刪除操作概念 | 編碼/解碼視樹深度,一般 O(n) | # 排序 Sorting Algorithms **排序算法是將一組資料按照特定順序重新排列的過程。** ## 冒泡排序 Bubble Sort - **概念:** 重複地遍歷要排序的資料列,依次比較相鄰的元素,若順序錯誤則交換位置,直到整個資料列有序為止。 - **時間複雜度:** 最佳 O(n^2),最差 O(n^2) - **虛擬碼 Pseudocode:** ``` for i from 0 to n-1 do for j from 0 to n-i-2 do if A[j] > A[j+1] then swap A[j] and A[j+1] ``` - **範例:** 排序: `A[] = {5, 2, 9, 1, 5, 6}` 1. 比較 A[0] 和 A[1],因為 5 > 2,交換位置: {2, 5, 9, 1, 5, 6} 2. 比較 A[1] 和 A[2],因為 5 < 9,不交換位置: {2, 5, 9, 1, 5, 6} 3. 比較 A[2] 和 A[3],因為 9 > 1,交換位置: {2, 5, 1, 9, 5, 6} 4. 重複以上步驟直到徹底掃描整個陣列 最終結果: `A[] = {1, 2, 5, 5, 6, 9}` ## 插入排序 Insertion Sort - **概念:** 將資料分為已排序和未排序兩部分,從未排序部分取出一個元素,將其插入到已排序部分的適當位置,重複此過程直到所有元素均排序完成。 - **時間複雜度:** 最佳 O(n),最差 O(n^2) - **虛擬碼 Pseudocode:** ``` for i from 1 to n-1 do key = A[i] j = i - 1 while j >= 0 and A[j] > key do A[j + 1] = A[j] j = j - 1 A[j + 1] = key ``` - **範例:** 排序: `A[] = {5, 2, 9, 1, 5, 6}` 1. 將 A[1] (2) 插入到已排序部分 {5},結果: {2, 5} 2. 將 A[2] (9) 插入到已排序部分 {2, 5},結果: {2, 5, 9} 3. 將 A[3] (1) 插入到已排序部分 {2, 5, 9},結果: {1, 2, 5, 9} 4. 重複以上步驟直到徹底掃描整個陣列 最終結果: `A[] = {1, 2, 5, 5, 6, 9}` ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-68.png) ## 二元樹排序 Tree Sort - **概念:** 將資料插入到二元搜尋樹中,然後進行中序遍歷以獲得排序後的資料。 - **時間複雜度:** 最佳 O(n log n),最差 O(n^2) ## 堆積排序 Heap Sort - **概念:** 將資料構建成最大或最小堆積,然後反覆取出堆頂元素並重建堆積,直到所有元素均排序完成。 - **時間複雜度:** 最佳 O(n log n),最差 O(n log n) ## 選擇排序 Selection Sort - **概念:** 重複地從未排序部分選擇最小(或最大)元素,將其放置在已排序部分的末尾,直到所有元素均排序完成。 - **時間複雜度:** 最佳 O(n^2),最差 O(n^2) - **虛擬碼 Pseudocode:** ``` for i from 0 to n-1 do minIndex = i for j from i+1 to n-1 do if A[j] < A[minIndex] then minIndex = j swap A[i] and A[minIndex] ``` - **範例:** 排序: `A[] = {5, 2, 9, 1, 7, 6}` 1. 找到最小元素 1,交換位置: {1, 2, 9, 5, 7, 6} 2. 找到最小元素 2,位置不變: {1, 2, 9, 5, 7, 6} 3. 找到最小元素 5,交換位置: {1, 2, 5, 9, 7, 6} 4. 重複以上步驟直到徹底掃描整個陣列 最終結果: `A[] = {1, 2, 5, 6, 7, 9}` ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-69.png) ## 合併排序 Merge Sort - **概念:** 將資料遞迴地分割成兩半,直到每個子陣列只有一個元素,然後將子陣列合併回來,並在合併過程中進行排序。 - **時間複雜度:** 最佳 O(n log n),最差 O(n log n) - **虛擬碼 Pseudocode:** ``` function mergeSort(A, left, right) if left < right then mid = (left + right) / 2 mergeSort(A, left, mid) mergeSort(A, mid + 1, right) merge(A, left, mid, right) function merge(A, left, mid, right) n1 = mid - left + 1 n2 = right - mid create arrays L[0..n1-1] and R[0..n2-1] for i from 0 to n1-1 do L[i] = A[left + i] for j from 0 to n2-1 do R[j] = A[mid + 1 + j] i = 0; j = 0; k = left while i < n1 and j < n2 do if L[i] <= R[j] then A[k] = L[i] i = i + 1 else A[k] = R[j] j = j + 1 k = k + 1 while i < n1 do A[k] = L[i] i = i + 1 k = k + 1 while j < n2 do A[k] = R[j] j = j + 1 k = k + 1 ``` - **範例:** 排序: `A[] = {5, 2, 9, 1, 7, 6}` (單次 merge 過程,目前有兩陣列 L[] = {2,5,9} 及 R[] = {1,6,7} ) - 首先合併兩陣列為 A[] = {2,5,9,1,6,7},並建立目標陣列 T[] = {}。 - 設定指標: I = 0, J = 3 使 A[I] = 2, A[J] = 1。 - 開始比較 A[I] 及 A[J]: 1. 因為 A[I] > A[J],將 A[J] 放入 T[],並使 J 右移,A[J] = 6,T[] = {1}。 2. 因為 A[I] < A[J],將 A[I] 放入 T[],並使 I 右移,A[I] = 5,T[] = {1,2}。 3. 因為 A[I] < A[J],將 A[I] 放入 T[],並使 I 右移,A[I] = 9,T[] = {1,2,5}。 4. 因為 A[I] > A[J],將 A[J] 放入 T[],並使 J 右移,A[J] = 7,T[] = {1,2,5,6}。 5. 因為 A[I] > A[J],將 A[J] 放入 T[],並使 J 右移,J 超出範圍,T[] = {1,2,5,6,7}。 6. 將剩餘的 A[I] 放入 T[],T[] = {1,2,5,6,7,9}。 最終結果: `A[] = {1, 2, 5, 6, 7, 9}` (若此次 merge 不是最後一次,則根據上述虛擬碼繼續執行合併) ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-70.png) ## 快速排序 Quick Sort - **概念:** 選擇一個基準元素(pivot),將資料分割成兩部分,一部分小於基準元素,另一部分大於基準元素,然後遞迴地對這兩部分進行排序。 - **時間複雜度:** 最佳 O(n log n),平均 O(n log n),最差 O(n^2) - **虛擬碼 Pseudocode:** ``` function quickSort(A, low, high) if low < high then pivotIndex = partition(A, low, high) quickSort(A, low, pivotIndex - 1) quickSort(A, pivotIndex + 1, high) function partition(A, low, high) pivot = A[high] i = low - 1 for j from low to high - 1 do if A[j] <= pivot then i = i + 1 swap A[i] and A[j] swap A[i + 1] and A[high] return i + 1 ``` - **範例:** 排序: `A[] = {5, 2, 9, 1, 7, 6}` (單次 partition 過程) 1. 選擇基準元素 5 進行 partition,使 left 及 pivot 在其上,right 在 6 上。 2. 因為 right > pivot,right 左移至 7(仍然合法)。 3. 直至 right 移至 1(不合法),停止。 4. 交換 left 及 right 的值,並使 pivot 仍然於 5 的位置,結果: {1, 2, 9, 5, 7, 6} 5. left 在 1 上,尚未達到停止條件,繼續右移至 2(合法),再右移至 9(不合法),停止。 6. 交換 left 及 right 的值,並使 pivot 仍然於 5 的位置,結果: {1, 2, 5, 9, 7, 6} 7. right 在 9 上,尚未達到停止條件,繼續左移至 5,此時 left 與 right 相遇,停止。 最終結果: `A[] = {1, 2, 5, 6, 7, 9}` ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-98.png) ## 基數排序 Radix Sort - **概念:** 根據每個元素的位數,從最低位到最高位依次進行排序,通常使用穩定排序算法(如計數排序)作為子排序算法。 - **時間複雜度:** O(kn),其中 k 為最大數字的位數。 ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-71.png) ## 希爾排序 Shell Sort - **概念:** 將資料的長度除以 2 為 gap,把每單位 gap 的資料切開疊起來,縱向即為子陣列,對每個子陣列做插入排序,重複此過程直到 gap 為 1。 - **時間複雜度:** 最佳 O(n),最差 O(n^2) - **虛擬碼 Pseudocode:** ``` function shellSort(A) gap = length(A) while gap > 1 do gap = (gap + 1) / 2 for i from 0 to gap - 1 do insertionSortWithGap(A, i, gap) ``` - **範例:** 排序: `A[] = {5, 2, 9, 1, 7, 6}` 1. 初始 gap = 6,更新 gap = 3,分成三個子陣列: {5, 1}, {2, 7}, {9, 6}。 2. 對每個子陣列進行插入排序,結果: {1, 5}, {2, 7}, {6, 9},合併後: {1, 2, 6, 5, 7, 9}。 3. 更新 gap = 2,分成兩個子陣列: {1, 6, 7}, {2, 5, 9}。 4. 對每個子陣列進行插入排序,結果: {1, 5, 6, 7}, {2, 9},合併後: {1, 2, 5, 6, 7, 9}。 5. 更新 gap = 1,對整個陣列進行插入排序,結果: {1, 2, 5, 6, 7, 9}。 最終結果: `A[] = {1, 2, 5, 6, 7, 9}` ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-99.png) ## 性質-穩定性 Stability - **穩定排序 Stable Sort:** 若兩個元素相等,排序後其相對位置不變。例如: 冒泡排序、插入排序、合併排序、二元樹排序、基數排序。 - **不穩定排序 Unstable Sort:** 若兩個元素相等,排序後其相對位置可能改變。例如: 選擇排序、快速排序、堆積排序、歇爾排序。 ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-72.png) ## 各種排序算法比較 Comparison of Sorting Algorithms | 演算法名稱 | 英文名稱 | 最佳時間複雜度 (Best) | 最差時間複雜度 (Worst) | 穩定性 (Stability) | | ---------- | ---------------- | --------------------- | ---------------------- | ------------------ | | 冒泡排序 | Bubble Sort | O(n^2) | O(n^2) | 穩定 (Stable) | | 插入排序 | Insertion Sort | O(n) | O(n^2) | 穩定 (Stable) | | 選擇排序 | Selection Sort | O(n^2) | O(n^2) | 不穩定 (Unstable) | | 合併排序 | Merge Sort | O(nlogn) | O(nlogn) | 穩定 (Stable) | | 快速排序 | Quick Sort | O(nlogn) | O(n^2) | 不穩定 (Unstable) | | 二元樹排序 | Binary Tree Sort | O(nlogn) | O(n^2) | 穩定 (Stable)\* | | 堆積排序 | Heap Sort | O(nlogn) | O(nlogn) | 不穩定 (Unstable) | | 希爾排序 | Shell Sort | \*難以定論 | O(n^2) \*難以定論 | 不穩定 (Unstable) | | 基數排序 | Radix Sort | O(nk) | O(nk) | 穩定 (Stable) | **備註:** - \* 二元樹排序的穩定性取決於插入元素時的處理方式,若在插入相等元素時保持其相對順序,且使用中序遍歷表示,則可視為穩定排序。 - 基數排序的時間複雜度 O(nk) 中,n 為元素數量,k 為最大元素的位數。若 k 很大,則基數排序的效率可能不如其他 O(nlogn) 的排序算法。 - 希爾排序的時間複雜度難以定論,因為其效率高度依賴於所選擇的間隔序列(gap sequence)。 ## 搜尋 Searching Algorithms **搜尋算法是用於在資料結構中查找特定元素的過程。** - ### 線性搜尋 Linear Search - **概念:** 適用於未排序的資料結構,從資料結構的第一個元素開始,逐一比較每個元素,直到找到目標元素或遍歷完整個資料結構。 - ### 二元搜尋 Binary Search - **概念:** 適用於已排序的資料結構,通過反覆將搜尋範圍縮小一半來查找目標元素。 - ### 插值搜尋 Interpolation Search - **概念:** 適用於均勻分布的已排序資料結構,根據目標元素與資料範圍的比例來估算下一個搜尋位置。 - ### 跳躍搜尋 Jump Search - **概念:** 適用於已排序的資料結構,通過跳躍固定步長來快速縮小搜尋範圍,然後進行線性搜尋。 # 圖 Graphs **圖是一種由節點(vertices/nodes)和邊(edges)組成的資料結構,用於表示物件之間的關係。** ## 無向圖 Undirected Graph ### 定義 Definition 一個圖 G 可以表示為一對 G = (V, E),其中: - V(G) 是節點的集合,稱為頂點集(vertex set),每個節點代表一個物件。 - E(G) 是邊的集合,稱為邊集(edge set),每條邊連接兩個節點,表示它們之間的關係。 下圖為一個包含 5 個節點和 6 條邊的無向圖,其中: - V(G) = {A, B, C, D, E} - E(G) = {(A, B), (A, D), (B, C), (B, D), (C, E), (D, E)} ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-73.png) ### 術語 Terminology - **鄰接節點 Adjacent Nodes/neighbors:** 對每條邊來說,其中 e = (u, v) 連接節點 u 和 v,則 u 和 v 是彼此的鄰接節點。 - **度 Degree:** 節點 u 的度 deg(u) 是指與節點 u 相連的邊的數量。若 deg(u) = 0,則稱節點 u 為孤立節點(isolated node)。 - **路徑 Path:** 在圖中,一條路徑是由一系列相鄰節點組成的序列,表示從一個節點到另一個節點的連接方式。路徑 P 可以表示為 P = (v1, v2, ..., vk),其中每對相鄰節點 (vi, vi+1) 都存在於邊集 E(G) 中。若 v1 = vk,則稱該路徑為閉路(closed path)。 - **簡單路徑 Simple Path:** 若路徑中沒有重複的節點,則稱該路徑為簡單路徑。(例外: 起點與終點可相同,形成閉路,稱為簡單閉路 closed simple path) ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-74.png) - **迴路 Cycle:** 若路徑的起點和終點是同一個節點,且路徑中至少包含一條邊,則稱該路徑為迴路,同於閉路。簡單迴路(simple cycle)是指除了起點和終點外,路徑中沒有重複的節點,同於簡單閉路。 ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-75.png) - **正則圖 Regular Graph:** 若圖中所有節點的度相同,則稱該圖為正則圖。 ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-76.png) - **連通圖 Connected Graph:** 若圖中任意兩個節點之間都存在路徑(無孤立節點),則稱該圖為連通圖。 ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-77.png) - **完全圖 Complete Graph:** 若圖中每對不同的節點之間都存在一條邊,則稱該圖為完全圖。且完全圖中節點數為 n 的圖,邊數為 n(n-1)/2。 ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-78.png) - **團 Clique:** 圖中的一個子集 S ⊆ V(G),若 S 中的每對不同節點之間都存在一條邊,則稱 S 為圖 G 的一個團。 ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-79.png) - **迴圈 Loop:** 若圖中存在一條邊連接節點 u 到自身,即 e = (u, u),則稱該邊為迴圈。 - **多重邊 Multiedge:** 若圖中存在多條邊連接同一對節點 u 和 v,即 e1 = (u, v) 和 e2 = (u, v),則稱這些邊為多重邊。 - **多重圖 Multigraph:** 若圖中允許存在迴圈和多重邊,則稱該圖為多重圖。 - **圖的大小 Size of a Graph:** 圖 G 的大小定義為其邊的總數,記為 |E(G)|。 - **關節點 Articulation Point:** 在連通圖中,若刪除節點 v 及其相關邊後,圖變為不連通,則稱節點 v 為關節點。 ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-80.png) - **雙連通圖 Biconnected Graph:** 若圖中不存在關節點,則稱該圖為雙連通圖。 - **橋 Bridge:** 在連通圖中,若刪除邊 e 後,圖變為不連通(Disconnected),則稱邊 e 為橋。 ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-81.png) ## 有向圖 Directed Graph ### 定義 Definition 一個有向圖 G 可以表示為一對 G = (V, E),其中: - V(G) 是節點的集合,稱為頂點集(vertex set),每個節點代表一個物件。 - E(G) 是有向邊的集合,稱為邊集(edge set),每條有向邊連接兩個節點,表示從一個節點指向另一個節點的關係。 下圖為一個包含 5 個節點和 6 條有向邊的有向圖,其中: - V(G) = {A, B, C, D, E} - E(G) = {(A, B), (A, D), (B, D), (C, B), (D, E), (E, C)}其中(u, v)表示有向邊從節點 u 指向節點 v(u -> v)。 ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-82.png) ### 術語 Terminology - **入度 Indegree:** 節點 v 的入度 indeg(v) 是指指向節點 v 的有向邊的數量。 - **出度 Outdegree:** 節點 u 的出度 outdeg(u) 是指從節點 u 指向其他節點的有向邊的數量。 - **度 Degree:** 節點 u 的度 deg(u) 是指與節點 u 相連的有向邊的總數,即 deg(u) = indeg(u) + outdeg(u)。 - **源點 Source Node:** 若節點 u 的入度 indeg(u) = 0 且 outdeg(u) > 0,則稱節點 u 為源點。 - **匯點 Sink Node:** 若節點 v 的出度 outdeg(v) = 0 且 indeg(v) > 0,則稱節點 v 為匯點。 - **吊墜節點 Pendant Node:** 若節點 w 的度 deg(w) = 1,則稱節點 w 為吊墜節點。 - **可及性 Reachability:** 在有向圖中,若存在一條有向路徑從節點 u 指向節點 v,則稱節點 v 可由節點 u 可及(reachable)。 - **平行/多重邊 Parallel/Multiedge:** 若有向圖中存在多條有向邊連接同一對節點 u 和 v,即 e1 = (u, v) 和 e2 = (u, v),則稱這些邊為平行/多重邊。 - **強連通有向圖 Strongly Connected Directed Graph:** 若有向圖中任意兩個節點 u 和 v 之間都存在有向路徑從 u 指向 v 且從 v 指向 u,則稱該有向圖為強連通有向圖。 ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-83.png) - **弱連通有向圖 Weakly Connected Directed Graph:** 若將有向圖中的所有有向邊視為無向邊後,所得的無向圖為連通圖,則稱該有向圖為弱連通有向圖。 ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-84.png) - **遞移閉包 Transitive Closure:** 在有向圖中,若存在有向路徑從節點 u 指向節點 v,則在遞移閉包中也存在一條直接的有向邊從 u 指向 v。換句話說,遞移閉包這張圖記錄了一點能不能走到另一點。 ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-85.png) ## 最小生成樹 Minimum Spanning Tree (MST) **生成樹是一個連通無向圖的子集,一張圖可以有很多生成樹。生成樹必須是連通(connected)、無向(undirected)、無迴圈(No Loop)的。** **而最小生成樹則是在所有可能的生成樹中,在為每條邊賦予權重(weight)的情況下,選擇總權重相等或是小於其他生成樹的生成樹。** **可能會有很多的最小生成樹,取決於邊的權重分配。若所有邊的權重皆相同,則所有生成樹皆為最小生成樹;若邊的權重皆不同,則最小生成樹為唯一。而對於沒有權重的圖,可以將所有邊的權重視為相同,則所有生成樹皆為最小生成樹。** ### 普林演算法 Prim's Algorithm **普林演算法是一種用於尋找最小生成樹的貪心算法,其核心思想是從一個節點開始,逐步擴展生成樹,直到包含所有節點為止。** **口訣: `找鄰居->選最小->加入樹`** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-86.png) ### 克魯斯克爾演算法 Kruskal's Algorithm **克魯斯克爾演算法是一種用於尋找最小生成樹的貪心算法,其核心思想是從所有邊(E(G))中選擇權重最小的邊,並將其加入生成樹中,直到包含所有節點為止。若該圖未連通,則會找到最小生成森林。** **口訣: `選小邊->加不通`** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-87.png) ### 通用規則 general formulation ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-88.png) **在上述兩種演算法中,都以各自的準則選擇所謂的安全邊(safe edge),即在不形成迴路的情況下,將邊加入生成樹中。** - **普林演算法:** 從生成樹的節點出發,選擇與生成樹相連且權重最小的邊作為安全邊。 - **克氏演算法:** 從所有邊中選擇權重最小的邊,若該邊連接的兩個節點不曾經被連結過,則將其加入生成樹中作為安全邊。 ## 圖的表示法 Graph Representation **圖可以透過多種方式來表示,在電腦中儲存圖有三種常見方式:** - **串列表示(Sequential Representation):** 使用鄰接矩陣(adjacency matrix)來表示圖,其中矩陣的行和列分別代表節點,矩陣中的元素表示節點之間是否存在邊。 - **鏈結表示(Linked Representation):** 使用鄰接串列(adjacency list)來表示圖,其中每個節點都有一個鏈接列表(Link List),用來儲存節點的鄰居。 - **鄰接多重列表表示(Adjacency Multilist Representation):** 算是鄰接串列的進階版。 ### 鄰接矩陣 Adjacency Matrix **鄰接矩陣以一個 n x n 的二維陣列來表示一個有 n 個節點的圖,其中矩陣中的元素表示節點 n1 和節點 n2 之間是否存在邊,存在記為 1,不存在記為 0。(因此也被稱作比特矩陣 bit matrix 或是布林矩陣 boolean matrix)** **一個一階的鄰接矩陣 A^1 表示圖中節點之間的直接連接關係,而更高階的鄰接矩陣 A^k 則表示節點之間是否可以透過 k 條邊連接起來。我們可以透過(A^1)^k 來計算。** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-90.png) **鄰接矩陣還有一些推廣如下:** **`B^k = A^1 + A^2 + ... + A^k`。** **path matrix P 設定若存在從節點 i 到節點 j 的路徑,則 P[i][j] = 1,否則為 0。** 其他的一些鄰接矩陣: ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-89.png) ### 鄰接串列 Adjacency List **鄰接串列通常被用來儲存邊數少至中等的圖,在電腦記憶體中,鄰接矩陣更適合用來儲存稀疏圖(sparse graph),而鄰接串列則更適合用來儲存稠密圖(dense graph)。** 一些鄰接串列的範例: ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-91.png) ### 鄰接多重列表 Adjacency Multilist **鄰接多重列表是鄰接列表的改良,他以基於邊的形式儲存資料,而藉由這個資料反向推出頂點的鄰接關係。** 範例: ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-92.png) ## 圖的遍歷 Graph Traversal **圖的遍歷是指從圖中的一個節點開始,按照一定的規則訪問圖中的所有節點和邊的過程。常見的圖遍歷算法有深度優先搜尋(Depth-First Search, DFS)和廣度優先搜尋(Breadth-First Search, BFS)。** ### 廣度優先搜尋 Breadth-First Search (BFS) **使用隊列(queue)來實現,從起始節點開始,先訪問該節點的所有鄰接節點,然後再依次訪問這些鄰接節點的鄰接節點,直到所有節點都被訪問為止。** **在樹上對應到階序遍歷(level-order traversal)。** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-93.png) ### 深度優先搜尋 Depth-First Search (DFS) **使用堆疊(stack)來實現,從起始節點開始,沿著一條路徑一直深入訪問節點,直到無法繼續深入為止,然後回溯到上一個節點,繼續訪問其他未被訪問的鄰接節點,直到所有節點都被訪問為止。** **在樹上對應到前序遍歷(pre-order traversal)。(樹的前序、中序、後序都可以透過 DFS 實現)** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-94.png) ## 最短路徑演算法 Shortest Path Algorithms **給定一張圖 G = (V, E),其中 V 是節點集合,E 是邊集合,每條邊 (u, v) 有一個權重 w(u, v)。最短路徑問題是尋找從起始節點 s 到目標節點 t 的路徑,使得該路徑上所有邊的權重總和最小。** ### 戴克斯特拉演算法 Dijkstra's Algorithm **戴克斯特拉演算法是一種用於尋找單源最短路徑的貪心算法,適用於邊權重非負的圖(有向圖或無向圖皆可)。其核心思想是從起始節點開始,將周遭的節點依照距離進行更新,並選擇距離最短的節點進行擴展,直到找到目標節點或所有節點的最短距離都被確定。** **口訣: `選最近->放入已知->更新鄰居`** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-95.png) **以此無向圖為例:** - 選定起點 D,放入已知集合 S={D}並標註為 0,更新鄰居 F = 5, B = 15, G = 23,而其他未知點保持無限大。 - 選擇距離最近的節點 F,放入已知集合 S={D, F}並標註為 5,更新鄰居 C = 14, G = 18 (18 < 23,更新為 18)。 - 選擇距離最近的節點 C,放入已知集合 S={D, F, C}並標註為 9,更新鄰居 A = 16。 - 選擇距離最近的節點 B,放入已知集合 S={D, F, C, B}並標註為 15,更新鄰居 E = 32,鄰居 A 有新的權重 20,但 20 > 原權重 16,故不更新。 - 選擇距離最近的節點 A,放入已知集合 S={D, F, C, B, A}並標註為 16,沒有新的可及的鄰居,不更新。 - 選擇距離最近的節點 G,放入已知集合 S={D, F, C, B, A, G}並標註為 18,更新鄰居 E = 29 (29 < 32,更新為 29)。 - 選擇距離最近的節點 E,放入已知集合 S={D, F, C, B, A, G, E}並標註為 29,此時所有節點均已放入已知集合,演算法結束。 ### 貝爾曼-福特演算法 Bellman-Ford Algorithm **貝爾曼-福特演算法是一種用於尋找單源最短路徑的動態規劃算法,適用於邊權重可以為負的圖(有向圖或無向圖皆可)。其核心思想是通過反覆鬆弛(relaxation)邊來更新節點的最短距離,直到所有節點的最短距離都被確定。** **演算法會返回一個布林值,表示是否存在從起點到其他節點的負權重迴路(negative-weight cycle)。若存在負權重迴路,則無法找到最短路徑,演算法返回 false;否則返回 true,並產生最短路徑以及節點各自的權重。** **口訣: `重複鬆弛->更新距離`** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-96.png) **以此有向圖為例:** - **首先列出所有邊及權重:** - (t, x) = 5 - (t, y) = 8 - (t, z) = -4 - (x, t) = -2 - (y, x) = -3 - (y, z) = 9 - (z, x) = 7 - (z, s) = 2 - (s, t) = 6 - (s, y) = 7 - **選定起點 s,初始化距離: d(s) = 0,其他節點距離皆為無限大。** 1. 第一次鬆弛: - 使用邊 (s, t): 更新 d(t) = 6 (s -> t) - 使用邊 (s, y): 更新 d(y) = 7 (s -> y) - 其他無限大,無意義 2. 第二次鬆弛: - 使用邊 (t, x): 更新 d(x) = 11 (6 + 5 = 11 < ∞) - 使用邊 (t, y): 無更新 (6 + 8 = 14 > 7) - 使用邊 (t, z): 更新 d(z) = 2 (6 + (-4) = 2 < ∞) - 使用邊 (x, t): 無更新 (∞ + (-2) = ∞ > 6) - 使用邊 (y, x): 更新 d(x) = 4 (7 + (-3) = 4 < 11) - 使用邊 (y, z): 無更新 (7 + 9 = 16 > 2) - 使用邊 (z, x): 無更新 (2 + 7 = 9 > 4) - 使用邊 (z, s): 無更新 (2 + 2 = 4 > 0) - 使用邊 (s, t): 無更新 (0 + 6 = 6 > 6) - 使用邊 (s, y): 無更新 (0 + 7 = 7 > 7) 3. 第三次鬆弛... **持續進行鬆弛直到完成 V-1 次(其中 V 為節點數量),並進入 check 階段,用以確認是否有負權重迴路。** ![alt text](https://raw.githubusercontent.com/rytlebsk/DS-2025/master/image-97.png) - check: - 使用邊 (t, x): 無更新 (4 < 2 + 5) - 使用邊 (t, y): 無更新 (7 < 2 + 8) - 使用邊 (t, z): 無更新 (-2 = 2 + (-4)) - 使用邊 (x, t): 無更新 (2 = 4 + (-2)) - 使用邊 (y, x): 無更新 (4 = 7 + (-3)) - 使用邊 (y, z): 無更新 (-2 < 7 + 9) - 使用邊 (z, x): 無更新 (4 < -2 + 7) - 使用邊 (z, s): 無更新 (0 = -2 + 2) - 使用邊 (s, t): 無更新 (2 < 0 + 6) - 使用邊 (s, y): 無更新 (7 = 0 + 7) **所有邊皆無法更新距離,表示不存在負權重迴路,成功找到最短路徑及各節點權重,演算法結束。** ### 比較 | 演算法 | 非負權重圖 | 負權重圖 | 負權重迴圈圖 | | ---------------------------------------- | ---------- | -------- | ------------ | | 戴克斯特拉演算法 Dijkstra's Algorithm | 支援 | 不支援 | 不支援 | | 貝爾曼-福特演算法 Bellman-Ford Algorithm | 支援 | 支援 | 支援 | **由此可知,貝爾曼-福特演算法較為通用,但在非負權重圖中,戴克斯特拉演算法通常更有效率。** **而二者所產生的最短路徑有可能不同,但是節點的權重最終會相同。** --- # [本學期結束]

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password
    or
    Sign in via Google Sign in via Facebook Sign in via X(Twitter) Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    By signing in, you agree to our terms of service.

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully