sjshyu
    • 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
    2
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # 資料結構與演算法 習題解答 奇數題 ## 第一章 基本概念 --- ### 1. 寫一個程序,將傳入的整數參數 x、y 和 z 由小到大印出。此程序的計算時間為多少? :::spoiler 可利用 bubble sort 的概念: ```cpp= # include <stdio.h> # include <iostream> using namespace std; void print_nondecreasingly(int x, int y, int z) { int t; if (x > y) SWAP(x, y, t); if (y > z) SWAP(y, z, t); if (x > y) SWAP(x, y, t); cout << "sorted: " << x << " " << y << " " << z << endl << endl; } int main() { int x, y, z; while (cout << "x y z = ", scanf("%d %d %d",&x,&y,&z), x!=0) print_nondecreasingly(x, y, z); } ``` 時間複雜度為 *O*(1)。 /* ---- 執行結果: x y z = 34 12 9 sorted: 9 12 34 x y z = 23 45 28 sorted: 23 28 45 x y z = 9 2 7 sorted: 2 7 9 x y z = 0 9 11 ---- */ ![](https://i.imgur.com/FiBk7NR.png) ::: --- ### 3. 費氏 (Fibonacci) 數列被定義成: <br> &emsp; (1) $F_0 = 0, F_1 = 1;$ <br> &emsp; (2) $F_n= F_{n−1} + F_{n−2},$ 當 $n≥2$。 <br> 寫出遞迴和迴圈版本(非遞迴)的程序來計算費氏數列(輸入 $n$,求 $F_1, F_2, … , F_n$)。 :::spoiler 遞迴版本: ```cpp= int F(int n) { int ans; if (n==0) return 0; if (n==1) return 1; return F(n-1)+F(n-2); } int main(void) { int n, i; while(cin >> n) { for (i=0; i<=n; i++) cout << F(i )<< " "; } } ``` /* ---- 執行結果: 3 0 1 1 2 5 0 1 1 2 3 5 10 0 1 1 2 3 5 8 13 21 34 55 15 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 20 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 ---- */ ![](https://i.imgur.com/lZOxbmN.png) ::: :::spoiler 迴圈版本: ```cpp= int main() { int n, i; while (cin>>n) { int F[n+1] = {0}; F[1] = 1; for (i=2; i<=n; i++) F[i]=F[i-1]+F[i-2]; for (i=0; i<=n; i++) cout<<F[i]<<" "; cout<<endl<<endl; } } ``` /* ---- 執行結果: 3 0 1 1 2 5 0 1 1 2 3 5 10 0 1 1 2 3 5 8 13 21 34 55 20 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 ---- */ ![](https://i.imgur.com/3vQfE2A.png) ::: --- ### 5. Ackerman’s 函式 A(m, n) 的定義如下: <br> $\qquad \quad A(m,n)=\begin{cases}\qquad \quad n+1 \qquad \qquad \quad \ \ \text{ if } \ \ m=0 \\\qquad A(m-1,1) \qquad \quad \ \ \ \text{ if } \ \ n=0 \\A(m-1,A(m,n-1)) \quad \text{ otherwise }\end{cases}$ <br> 這個函式在 $m$ 和 $n$ 的值還不是很大的時候,就已成長的非常快。寫一個遞迴程序和非遞迴程序計算它。 :::spoiler 遞迴版本: ```cpp= int Ackerman(int m,int n) { if (m==0) return n+1; else if (n==0) Ackerman(m-1,1); else Ackerman(m-1, Ackerman(m,n-1)); } int main(void) { int m, n; while(cin>>m>>n) { cout << "Answer = " << Ackerman(m,n); cout << endl << endl; } } ``` /* ---- 執行結果: 1 10 Answer = 12 3 10 Answer = 8189 2 15 Answer = 33 4 5 (等候逾時) ---- */ ![](https://i.imgur.com/Y6ofI0k.png) ::: ::: spoiler 迴圈版本: ```cpp= #include<iostream> #include<time.h> using namespace std; int ack_nonrecursive(int m, int n) { int value[500000]; int Size = 0; for (;;) { if (m == 0) { n++; if (Size-- == 0) break; m = value[Size]; continue; } if (n == 0) { m--; n = 1; continue; } int index = Size++; value[index] = m - 1; n--; } return n; } int main() { int m, n; cin>>m>>n; double START,END; for(int i=1; i<=m; i++) for(int j=1; j<=n; j++) { cout<<"mi="<<i<<" nj="<<j<<"Value="; START = clock(); cout<<ack_nonrecursive(i, j)<<endl; END = clock(); cout<<"Execution Time: "<<(END - START) / CLOCKS_PER_SEC<<"s"<<endl<<endl; } } ``` /* ---- 執行結果: ![](https://i.imgur.com/na6KtK8.png) ![](https://i.imgur.com/k57YZJ8.png) ::: :::spoiler 遞迴與迴圈執行時間比較 (CPU: RAM: OS: Windows 10): | 遞迴版執行時間 | 迴圈版執行時間 | | -------- | -------- | | ![](https://i.imgur.com/VatBWLg.png) | ![](https://i.imgur.com/15FH7n1.png) | ::: --- ### 7. 假如 S 是一個含有 $n$ 個元素的集合,則 S 的 power set 就是「所有 S 可能的子集合的集合」,例如:假如 S = {a, b, c} ,則 powerset(S) = {∅, {a}, {b}, {c}, {a, b}, {a, c}, {b, c}, {a, b, c}},寫一個遞迴程式來計算 powerset(S)。 :::spoiler 請見下表: &emsp;&emsp;&ensp;*S* &emsp; &emsp; &emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp; powerset(*S*)中元素 |{a, b, c}|{}|{c}|{b}|{b, c}|{a}|{a, c}|{a, b}|{a, b, c}| | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | |0/1 Bit|000|001|010|011|100|101|110|111| 由上表可知:求 S = \{a, b, c} 的 power set 相當於求 000 \~ 111 的所有 bit patterns! 可設一 0/1 Bit 陣列 (\|BIT\| = \|S\|) 與 S 的字元相互對應,recursively 求 Bit 的所有0/1 patterns: powerset(Bit[k,n]) = {Bit[k]=0 \|\| powerest(Bit[k+1,n])} ∪ {Bit[k]=1 \|\| powerest(Bit[k+1,n])} 其中 || 表示 0/1 patterns 的串接。呼叫 powerset(Bit[n-1,n]) 時即為 boundary condition 可依 Bit[0:n-1] 的0/1 pattern 印出對應的 subset of S. ```cpp= # include <stdio.h> # include <stdlib.h> char a[10] = { "abcdefgh" }; char b[10]; int Bit[10]; int cnt = 0; int solCnt; void printSubset(int Bit [], int n) { int i, index = 0; for (i=0; i<n; i++) if (Bit[i]) b[index++] = a[i]; b[index] = '\0'; printf("{%s}", b); if (++cnt<solCnt) printf(", "); } void powerSet(int k, int n) { if (k == n) printSubset(Bit, n); else { Bit[k] = 0; powerSet(k+1, n); Bit[k] = 1; powerSet(k+1, n); } } int main() { int i, n; printf("Please input the size (n) of the set: n = "); while (scanf(" %d", &n), solCnt = pow(2, n), n!=0) { printf("{"); powerSet(0, n); printf("}\nSize of Powerset = %d.\n\nContinue (0 for exit) n = ", cnt); cnt = 0; } return 1; } ``` /* ---- 執行結果: Please input the size (n) of the set: n = 3 {{}, {c}, {b}, {bc}, {a}, {ac}, {ab}, {abc}} Size of Powerset = 8. Continue (0 for exit) n = 4 {{}, {d}, {c}, {cd}, {b}, {bd}, {bc}, {bcd}, {a}, {ad}, {ac}, {acd}, {ab}, {abd}, {abc}, {abcd}} Size of Powerset = 16. Continue (0 for exit) n = 5 {{}, {e}, {d}, {de}, {c}, {ce}, {cd}, {cde}, {b}, {be}, {bd}, {bde}, {bc}, {bce}, {bcd}, {bcde}, {a}, {ae}, {ad}, {ade}, {ac}, {ace}, {acd}, {acde}, {ab}, {abe}, {abd}, {abde}, {abc}, {abce}, {abcd}, {abcde}} Size of Powerset = 32. Continue (0 for exit) n = 7 {{}, {g}, {f}, {fg}, {e}, {eg}, {ef}, {efg}, {d}, {dg}, {df}, {dfg}, {de}, {deg}, {def}, {defg}, {c}, {cg}, {cf}, {cfg}, {ce}, {ceg}, {cef}, {cefg}, {cd}, {cdg}, {cdf}, {cdfg}, {cde}, {cdeg}, {cdef}, {cdefg}, {b}, {bg}, {bf}, {bfg}, {be}, {beg}, {bef}, {befg}, {bd}, {bdg}, {bdf}, {bdfg}, {bde}, {bdeg}, {bdef}, {bdefg}, {bc}, {bcg}, {bcf}, {bcfg}, {bce}, {bceg}, {bcef}, {bcefg}, {bcd}, {bcdg}, {bcdf}, {bcdfg}, {bcde}, {bcdeg}, {bcdef}, {bcdefg}, {a}, {ag}, {af}, {afg}, {ae}, {aeg}, {aef}, {aefg}, {ad}, {adg}, {adf}, {adfg}, {ade}, {adeg}, {adef}, {adefg}, {ac}, {acg}, {acf}, {acfg}, {ace}, {aceg}, {acef}, {acefg}, {acd}, {acdg}, {acdf}, {acdfg}, {acde}, {acdeg}, {acdef}, {acdefg}, {ab}, {abg}, {abf}, {abfg}, {abe}, {abeg}, {abef}, {abefg}, {abd}, {abdg}, {abdf}, {abdfg}, {abde}, {abdeg}, {abdef}, {abdefg}, {abc}, {abcg}, {abcf}, {abcfg}, {abce}, {abceg}, {abcef}, {abcefg}, {abcd}, {abcdg}, {abcdf}, {abcdfg}, {abcde}, {abcdeg}, {abcdef}, {abcdefg}} Size of Powerset = 128. Continue (0 for exit) n = 0 Process returned 1 (0x1) execution time : 51.907 s Press any key to continue. ![](https://i.imgur.com/UVVyKcI.png) ---- */ ::: --- ### 9. 比較 $n^3$ 和 $2^n$ 這兩個函式,計算出哪個 $n$ 值會使後者大於前者。 :::spoiler 當 $n$ = 10 時, $n^3$ =1000 < $2^n$ = 1024; 當 $n$ > 10 時, $n^3$ < $2^n$。 ::: --- ### 11. 分析下列程式的時間複雜度—計算出 x 的值(初始值皆為0),並以大*O*表示其時間複雜度: ::: spoiler (a) ```cpp= for (i=1; i<=n; i++) for (j=1; j<=i; j++) for (k=1; k<=j; k++) x++; ``` (a) 利用重複物件的組合來解迴圈執行次數: $\quad {r+n-1\ \choose r} \\ ={3+n-1\ \choose 3} \\ =\frac{n(n+1)(n+2)}{6} \\ = O(n^3)$ (b) ```cpp= i=1; while (i<=n) { x++; i++; } ``` (b) $n$ = *O*($n$) (c\) ```cpp= for (i=1; i<=n; i++) for (j=1; j<=i; j*=2) x++; ``` (c\) *O*($nlogn$) 列出註標 i, j 的值,與當*時x++的計算次數,之後加總之。 | i | 1 | 2 | 3 | 4 | ... | n | |- |---| - | --| -|-----|-- | |j |1| 1, 2 | 1, 2| 1, 2, 4|...|1, 2, 4, ... $n$ | |# |1| 2 | 2| 3|...|$logn+1$ | |# |$log1+1$| $log2+1$ | $log3+1$| $log4+1$|...|$logn+1$ | $log1+1+log2+1+...+logn+1$ $=\sum_{k=1}^{n}logn+n$ $= log(1\times 2\times ... \times n)+n$ $= log(n!)+n \\ \le log(n^n)+n = nlogn+n\\ =O(nlogn)$ (d) ```cpp= for (i=1; i<=n; i*=2) for (j=1; j<=i; j++) x++; ``` (d) *O*($n$) | i | 1 | 2 | 4 | 8 | ... | n | |- |---| - | --| -|-----|-- | |j |1| 1,2 | 1,2,3,4| 1~8|...| 1~$n$ | |# |1| 2 | 4| 8|...|$n$ | |# |$2^0$| $2^1$ | $2^2$| $2^3$|...|$2^{logn}$ | $2^0+2^1+2^2+ ... + 2^{logn} \ (引用等比級數和的公式)\\ =\frac{2^0(2^{logn+1}-1)}{2-1} \\ =2^{logn+1}-1\\ =2^{logn}\times 2-1\\ =2n-1\\ =O(n)$ ::: --- ### 13. 證明下列等式是錯誤的: <br> &emsp; (a) $10n^2+9 = O(n)$ <br> &emsp; (b) $n^2logn = Θ(n^2)$ <br> &emsp; ($\text{c}$) $\frac{n^2}{logn} = Θ(n^2)$ <br> &emsp; (d) $n^32^n+6n^23^n = O(n^32^n)$ ::: spoiler (a) $g(n) = n$ $cn ≥ 10n^2+9$ 因為找不到合適的 $c, n_0$ 使得 $n ≥ n_0$ 後,$cn ≥ 10n^2+9$ (b) $n^2logn = Θ(n^2)$ 若為真,則存在常數 $c_1、c_2$ 和 $n_0$ (由定義可知 $n > n_0$),可得 到 $c_1n^2 ≤ n^2logn ≤ c_2n^2$。$n^2logn ≤ c_2n^2 => logn ≤ c_2$ , 選擇 $n > \max\{n_0, 2c_2\}$,會產生矛盾。 ($\text{c}$) $\frac{n^2}{logn} = Θ(n^2)$ 若為真,則存在常數 $c_1、c_2$ 和 $n_0$ (由定義可知 $n > n_0$),可得到 $c_1n^2 ≤ n^2/logn ≤ c_2n^2$。$c_1n^2 ≤ n^2/logn=> logn ≤ 1/c_1$ 會產生矛盾,因為左邊當 $n$ 遞增時會跟著遞增,但右邊則為常數。 (d) $g(n) = n^32^n$ $cn^32^n ≥ n^32^n+6n^23^n => cn2^n ≥ n2^n+6×3^n$ 因為找不到合適的 $c, n_0$ 使得 $n ≥ n_0$ 後,$cn^32^n ≥ n^32^n+6n^23^n$ ::: --- ## 第二章 陣列 習題解答 --- ### 1. 有多少元素可以存在以下的陣列中(註標範圍 [x:y] 中 x 表示下界,y表示上界): <br> &emsp; (a) A[0:n]; <br> &emsp; (b) B[-23:29]; <br> &emsp; ($\text{c}$) C[-1:n][1:m]; <br> &emsp; (d) D[-n:0][1:3]; :::spoiler (a) n+1 (b) 23+1+29 = 53 ($\text{c}$) (1+1+n)×m = m(n+2) (d) (n+1)×3 = 3(n+1) 註:C 的陣列宣告時只需要陣列型態、名稱和大小;其註標從 0 開始,而且沒有下界、上界範圍的考量。這個題目旨在希望讀者對陣列內的個數/內容與註標定址有較廣泛的聯想。若希望用 "下界...上界" 來定位陣列元素,可以用指標來完成。請看下面的例子: ![](https://i.imgur.com/RLkNd1E.png) 其中前三行是程式的設定(第3行以 p 指向 A[5] 的住址)、後四行表列展示 A[0], A[1], ... , A[9] 和 *(p-5), *(p-4), ... , *(p+4) 各自存放的內容;可以清楚看出 A[0] 與 *(p-5) 、A[1] 與 *(p-4)、 ... 、A[9] 與 *(p+4) 的值是一樣的;亦即:對 p 而言,可用 -5...9 中的註標來定址陣列 A。用指標的概念,可使陣列的註標定址關係有更大的彈性。 ::: --- ### 3. 寫一個程序來估算每個不同的字元在字串中出現的次數,用適當的資料來測試你的函式。 :::spoiler 以下程式以英文小寫字元 'a', 'b', ... , 'z' 為例。 ```cpp= # include <stdio.h> void charFrequency(char s[], int len) { int freq[26], i; char base = 'a'; for (i=0; i<26; i++) freq[i] = 0; for (i=0; i<len; i++) freq[s[i]-base]++; for (i=0; i<26; i++) if (freq[i] != 0) printf("%c : %d\n", base+i, freq[i]); } int main() { char s [256]; int len; while (scanf("%s", s)!=EOF) { printf("s = %s\n", s); for (len=0; s[len] != '\0'; len++); printf("len = %d\n", len); charFrequency(s, len); } return 1; } ``` /* ---- 執行結果: asgfagsfgafsgafsgfagsfagfsg s = asgfagsfgafsgafsgfagsfagfsg len = 27 a : 6 f : 7 g : 8 s : 6 trwretwretrwtertwretuqywuyuywueyuwyueywuyeuyw s = trwretwretrwtertwretuqywuyuywueyuwyueywuyeuyw len = 45 e : 7 q : 1 r : 6 t : 6 u : 8 w : 9 y : 8 nvjfnjvfnjvnfjnvjfnvjnfjnjfvfnfjnvjfnjvnjfnvjf s = nvjfnjvfnjvnfjnvjfnvjnfjnjfvfnfjnvjfnjvnjfnvjf len = 46 f : 11 j : 13 n : 13 v : 9 ---- */ ![](https://i.imgur.com/BCWc1Gq.png) ::: --- ### 5. 寫一個函式,它會接受一個字串 text 和兩個整數 start 和 length,這個函式會從字串 text 的第 start 個字元起刪除 length 個字元,而傳回產生新的字串。 :::spoiler ```cpp= char[] deleteText(char text[], int start, int length) { int k; int groot[256]; // int n=text.length(); for (i=0, k=0; i<length; i++) { if (i<start || i >= (start+length) ) groot[k++]=text[i]; } // if ( text[i] == “\0”) return text; groot[k] = '\0'; return groot; } // 胡乃文、楊駘、史孟仟 提供 1036 ``` ::: --- ### 7. 假設有 $n$ 個串列,$n > 1$,它們用一維陣列 space[$m$] 來表示。令 front[$i$] 是指到第 $i$ 個串列第一個元素的前一個位置,rear[$i$] 是指到第 $i$ 個串列最後一個元素的位置,假設 rear[$i$] ≤ front[$i+1$],$0 ≤ i < n$,且 front[$n$] = $m−1$,這些串列所能做的運算就是插入和刪除。 <br> &emsp; (a) 找出 front[$i$] 和 rear[$i$] 的適當的起始和終止條件。 <br> &emsp; (b) 寫一個程序 Insert(int i, int j, int item),使其能在串列 $i$ 的第 ($j-1$) 個元素後面插入 item,這個程序在 space 已經有了 $m$ 個元素的情況下,不允許插入的動作。 :::spoiler (a) 起始條件:front[$i$] = rear[$i$] = $i * ⎣\frac{m}{n}⎦ -1$, $0 ≦ i<n$ 且 front[$n$] = $m-1$。 刪除終止條件:只有當 front[$i$] = rear[$i$]成立時,第 $i$ 條字串會為空。 新增終止條件:只有當 rear[$i$] = front[$i+1$]成立時,第 $i$ 條字串已滿。 (b) ```cpp= int Insert(int i, int j, int item) { if ( rear[i] - front[i] < j-1) return 0; if (rear[i] == front[i+1]) //判斷第 i 條字串是否已滿 ListFull( ); if (rear[i] < front[i+1]) //space found { for (int k = rear[i]; k >= j; k--) space[k+1] = space[k]; space[j] = item; return 1; } else return 0; } ``` ::: --- ### 9. 當一個陣列的所有元素都排在其主對角線及其下方或上方,這個矩陣就叫做「三角矩陣」(triangular matrix)。圖 2-19 表示出「下三角」(lower triangular) 和「上三角」(upper triangular) 矩陣,其中非 0 元素標示為 X。 <br> &emsp; &emsp; ![](https://i.imgur.com/fXB6wr1.png) <br> 將 $n×n$ 的下(上)三角矩陣存放在記憶體中,0 的值不存,請為陣列元素[$i$][$j$] 求出其位址公式,$0 ≤ i, j ≤ n−1$(每個元素佔 $l$ 個字元組,而 α 為陣列第一個非 0 元素(位於[0][0])在記憶體中的位址)。 ::: spoiler (a) 下三角 以列為主: A[$i$][$j$]($i ≥ j$) = $α + l[\frac{i(i+1)}{2} + j]$ 以行為主: A[$i$][$j$]($i ≥ j$) = $α + l[\frac{(2n-j+1)j}{2} + (i −j)]$ ::: ::: spoiler (b) 上三角 以列為主:A[$i$][$j$]($i ≤ j$) = $α + l[\frac{(2n-i+1)i}{2} + (j−i)]$ 以行為主:A[$i$][$j$]($i ≤ j$) = $α + l[\frac{j(j+1)}{2} + i]$ ::: --- ### 11. 一個「三對角線矩陣」(tri-diagonal matrix) 為一個方陣,其中在主對角線及其相鄰的兩個對角線以外的元素均為 0 (如圖 2-20)。令 A 為一 $n×n$ 的三對角線矩陣,將 A 中三個對角線所形成帶狀區域上的元素,以列為優先,儲存在一個一維陣列 M 中(A[0][0] 存放在 M[0] 中)。設計一個計算公式,輸入 A[$i$][$j$] 時,可求得 $k$—A[$i$][$j$] 存在 M[$k$] 中,$0 ≤ i, j ≤ n−1$。 &emsp; &emsp; &emsp; &emsp; &emsp; &emsp; &emsp; &emsp; ![](https://i.imgur.com/EzuLV82.png) :::spoiler $k = 2i+j$ (讀者可與以下表格比對。) | |0 |1 |2 |3 |4 |...| |:-:|:-:|:-:|:-:|:-:|:-:|:-:| |0 |0 |1 | | | | | |1 |2 |3 |4 | | | | |2 | |5 |6 |7 | | | |3 | | |8 |9 |10 | | |...| | | | | | | ::: --- ### 12. 一個「方形帶狀矩陣」(square band matrix) $D_{n,a}$ 是一個 $n×n$ 的矩陣,其中所有可能的非零元素,均集中在以主對角線為中心的帶狀區域上(此帶狀區域外的元素皆為 0);此帶狀區域包括了主對角線與其上和其下各 a−1 條對角線(如圖 2-21 (a) 和 (b),圖 2-21 (b) 是一 $D_{5,2}$ 的例子)。請回答: <br> &emsp; (a) 在此帶狀 $D_{n,a}$ 中有多少個元素? <br> &emsp; (b) 對於帶狀 $D_{n,a}$ 中的元素 $d_{ij}$ 而言,$i$ 與 $j$ 之間的關係為何? <br> &emsp; ($\text{c}$) 假設帶狀 $D_{n,a}$ 以對角線為主,並從最低的對角線開始,循序儲存在陣列 A 中。例如,圖 2-21 <br> &emsp; (b) 中的帶狀矩陣 $D_{5,2}$ 的表示法如下: <br> ![](https://i.imgur.com/R1IPfO6.png) <br> 找出在 $D_{n,a}$ 的下帶狀區域中的任一元素 $d_{ij}$ 的定址公式,即輸入 $i$ 和 $j$,求出 $k$,$d_{ij}$ = A[$k$]。 <br> ![](https://i.imgur.com/cIZcJgX.png) <br> ![](https://i.imgur.com/tTiijFe.png) ::: spoiler 題12的解法為求解題13的重要基礎,特列於此 (a) 主對角線有 $n$ 個元素,以其為基準,上方的1, 2, ... , *a*-1 條對角線分別有 $n-1,\ n-2,\ ...\ ,\ n-a+1$ 個元素,如下圖左; | ![](https://i.imgur.com/wkNsnYS.png) | ![](https://i.imgur.com/1EyicUD.png) | |----|----| 於其下方的 *a*-1 條對角線亦然。加總所有元素可得: $\begin{split} \quad &n+2\sum\limits_{t=1}^{n-a+1}{n-t}\\ &=n+2[(n-1)+(n-2)+...+(n-a+1)] \\ &=n+2\frac{(2n-a)(a-1)}{2} \\ &=n+(2n-a)(a-1) \\ &=n+(2na-a^2-2n+a) \\ &=2na-a^2-n+a \end{split}$ 上式亦可列為: $\begin{eqnarray} n+2\sum\limits_{t=n-a+1}^{n-1}{t} &=& n+2[(n-a+1)+(n-a+2)+...+(n-1)] \nonumber \\ ~&=& n+2\frac{(2n-a)(a-1)}{2} \nonumber \\ ~&=& n+(2n-a)(a-1) \end{eqnarray}$ (b) 請參考上圖右,令主對角線為 $D$: (b.1) 若 $i=j$,$A[i][j]$ 恰在 $D$ 上; (b.2) 若 $i<j\ 且\ j-i = t,A[i][j]$ 在 $D$ 上方的第 $t$ 條對角線,$1 \leq t\leq a-1$; (b.3) 若 $i>j\ 且\ i-j = t,A[i][j]$ 在 $D$ 下方的第 $t$ 條對角線,$1 \leq t\leq a-1$。 總結以上討論:$|i - j| < a$。 (c\) 令 $k$ 為由最低對角線開始存放一維陣列中 $A[i][j]$ 的對應註標。如 (b) 所列: (c.1) 若 $i=j$,$A[i][j]$ 恰在 $D$ 上; 所有下帶狀元素先存,所以有 $n+2[(n-1)+(n-2)+...+(n-a+1) = \frac{(2n-a)(a-1)}{2}$ 個 存在 $D$ 元素之前;而且在 $A[i][j]=A[i][i]$ 之前,還有 $i$ 個 $D$ 中元素,所以 $k = i+\frac{(2n-a)(a-1)}{2}$。 (c.2) 先考慮 (b.3) 的情況:若 $i>j\ 且\ i-j = t$,$A[i][j]$ 在 $D$ 下方的第 $t$ 條對角線,$1 \leq t\leq a-1$。 此 $D$ 下方的第 $t$ 條對角線,元素有 $n-t$ 個且其起點的列註標即為 $t$,再往下共有註標為 $t+1,\ t+2,\ ...\,\ a-1$ 的$a-t-1$條對角線,元素分別為 $n-t-1,\ n-t-2,\ ...\,\ n-a+1$,所有下帶狀元素先存,所以元素有 $\quad (n-t-1)+(n-t-2)+\ ...\ +(n-a+1) \\ =\frac{(n-a+1+(n-t-1))(a-t-1)}{2} =\frac{(2n-a-t)(a-t-1)}{2}$ 在 $D$ 下第 $t$ 條對角線的 $A[i][j]$ 之前的元素,其列註標起點分別位於 $t,\ t+1,\ ...\,\ i-1$,計有 $(i-1)-t+1 = i-t = i-(i-j) = j$ 個(或直接想:不管那條下帶狀的對角線,行 $j$ 之前必有 $j$ 個元素)。於是 $A[i][j]$ 之前的元素總個數為: $\quad k = j+\frac{(2n-a-t)(a-t-1)}{2}$。 (c.3) 若 $i<j\ 且\ j-i = t$,$A[i][j]$ 在 $D$ 上方的第 $k$ 條對角線,$1 \leq k\leq a-1$。 $D$ 中元素共有 $n$ 個;所有下帶狀的元素個數為: $\quad (n-1)+(n-2)+\ ...\ +(n-a+1) \\ =\frac{(n-1+(n-a+1))(a-1)}{2} =\frac{(2n-a)(a-1)}{2}。$ 在 $D$ 上第 $1,\ 2, \ ...\,\ t-1$ 條對角線的元素共有: $\quad (n-1)+(n-2)+\ ...\ +(n-t+1) \\ =\frac{(n-1+(n-t+1))(t-1)}{2} =\frac{(2n-t)(t-1)}{2}。$ 在 $D$ 中第 $k$ 條對角線上 $A[i][j]$ 之前的元素有 $(j-1)-(t-1)=j-t=j-(j-i)=i$ 個 (或直接想:不管那條上帶狀的對角線,列 $i$ 之前必有 $i$ 個元素)。由以上可得: $k=i+\frac{(2n-t)(t-1)}{2}+\frac{(2n-a)(a-1)}{2}。$ 總結 (c.1)-(c.3) 可得 $k=\begin{cases} i+\frac{(2n-a)(a-1)}{2}, \ \text{if}\ (i-j)=0; \\ j+\frac{(2n-a-t)(a-t-1)}{2}, \ \text{if}\ (i-j)=t\ \text{where}\ 1\le t \le a-1; \\ i+\frac{(2n-a)(a-1)}{2}+\frac{(2n-t)(t-1)}{2}, \ \text{if}\ (j-i)=t\ \text{where}\ 1\le t \le a-1 \end{cases}$ ::: --- ### 13. 一個「一般帶狀矩陣」(generalized band matrix) $D_{n,a,b}$ 是一個 $n×n$ 的矩陣,其中所有可能的非零元素,皆集中在由主對角線以下的 $a−1$ 個對角線,主對角線和主對角線以上的 $b−1$ 個對角線,所形成的帶狀區域上(如圖 2-22)。 <br> &emsp; (a) 在此帶狀 $D_{n,a,b}$ 中有多少個元素? <br> &emsp; (b) 對於帶狀 $D_{n,a,b}$ 中的元素 $d_{ij}$ 而言,$i$ 與 $j$ 之間的關係為何? <br> &emsp; ($\text{c}$) 找出此帶狀 $D_{n,a,b}$ 在一維陣列 $B$ 中的循序表示法。對此表示法,設計一個函數 $address(n, a, b, i, j, B)$,根據傳入的參數 $n, a, b, i, j, B$,來決定在陣列 B 中,矩陣 $D_{n,a,b}$ 中的 $d_{ij}$ 值。 <br> &emsp; &emsp; &emsp; &emsp; &emsp; &emsp; &emsp; ![](https://i.imgur.com/cgQnzpI.png) ::: spoiler (a) (by S. J.) $\sum\limits_{i=0}^{a-1}{(b+i)}+\sum\limits_{i=a}^{n-b-1}{(a+b-1)}+\sum\limits_{i=n-b}^{n-1}{[(a+b-1)-(n-b-i)]}$ $=ab+ \frac{a(a-1)}{2}+(n-a-b)(a+b-1)+ab+\frac{b(b-1)}{2}$ $(=n(a+b-1)-\frac{a(a-1)+b(b-1)}{2})$ ![](https://i.imgur.com/O1fXEHK.jpg) 例子:$a=3,\ b=2,\ n=8$ 代入上式可得元素個數共 28 個 (見下圖右下) ![](https://i.imgur.com/TxABLQo.png) 亦可用對角線優先的觀點來想: 主對角線 $D$ 共 $n$ 個元素;其下帶狀 $a-1$ 條對角線分別有 $n-1,\ n-2,\ ...\ ,\ n-a+1$ 個元素;其上帶狀共 $b-1$ 條對角線分別有 $n-1,\ n-2,\ ...\ ,\ n-b+1$ 個元素;總共有 $\begin{aligned} \quad &n+\sum\limits_{k=n-a+1}^{n-1}{k}+\sum\limits_{k=n-b+1}^{n-1}{k} \\ &= n+[(n-a+1)+(n-a+2)+...+(n-1)]+[(n-b+1)+(n-b+2)+...+(n-1)] \\ &= n+\frac{(a-1)(2n-a)}{2}+\frac{(b-1)(2n-b)}{2} \\ &=\frac {2n+2na-2n-a^2+a+2nb-2n-b^2+b}{2} \\ &=n(a+b-1)-\frac{a(a-1)+b(b-1)}{2} \end{aligned}$ (b) 若 $d_{ij}$ (b.1) 在主對角線 $D$ 上,$i=j$; (b.2) 在下帶狀區域內,則 $(i-j)\le a-1$; (b.3) 在上帶狀區域內,則 $(j-i)\le b-1$;$|i - j| < \text{max}(a, b)$ 不在 (b.1)-(b.3) 範圍內的 $i, j$ 其 $d_{ij}$ = 0。 ($\text{c}$) 若以最低對角線開始存放元素(如題12),則 $address(n, a, b, i, j, B)$ 可以下式設計,回傳位址:$\&B[k]\ (=B+k (= \alpha+k\times l))$,其中 $k=\begin{cases} i+\frac{(a-1)(2n-a)}{2}, \ \text{if} \quad i-j=0 \\ j-a+\frac{(a-1-s)(2n-a-s)}{2-1}, \ \text{if} \quad i-j =s, \text{and} \quad s<a \\ i-a+n+\frac{(a-1)(2n-a)}{2}+\frac{(s-1)(2n-s)}{2-1}, \ \text{if} \quad j-i =s,\text{and} \quad s<b \end{cases}$ 若要以列為優先,則請依上述邏輯自行推導。讀者可由題12和13得知:用「對角線優先」(diagonal-major) 可以是二維陣列儲存在一維記憶體空間中,有效率地的對應關係。 ::: --- --- ## 第三章 堆疊和佇列 習題解答 --- ### 1. 圖 3-27 描繪了一個鐵路調度軌道,每一節車廂都編號 1, 2, 3, ..., $n$,並按順序從左到右停放在軌道上,車廂可以從任何一條橫向軌道一次一台的移進垂直軌道,而移進垂直軌道的車廂也可以一次一台的移到任何一條橫向軌道,則垂直軌道就像一個堆疊,新移進車廂都在最上層,能移出的車廂也是最上層的那一個。假設 $n$ = 3 時,我們可以先移車廂 1 進垂直軌道,再來是車廂 2,最後是車廂 3,然後我們就得到一個新的順序 3, 2, 1;當 $n$ = 3和 4 時,可以得到哪幾種有可能的車廂排列?有哪幾種排列是不可能的? <br> &emsp; &emsp; &emsp; &emsp; &emsp; &emsp; ![](https://i.imgur.com/awFSI5T.png) :::spoiler 數量可藉由離散數學中介紹的 Catalan 數列求一般解 當 n = 3 時 &ensp; 可能的排列有 5 種:123,132,213,231,321 &ensp; 不可能的排列有 1 種:312 當 n = 4 時 &ensp; 可能的排列有 14 種:1234,1243,1324,1342,1432,2134,2143,2314,2341, 2431,3421,3241,3214,4321 &ensp; 不可能的排列有10 種:1423,2413,3124,3142,3412,4123,4132,4213,4231,4312 註:Catalan Number $C_n = {2n\choose {n}}-{2n\choose {n+1}}$, or $C_n =\frac {1}{n+1}{2n \choose {n}}$ ::: --- ### 3. 一個「雙端佇列」(double-ended queue,也稱 deque)是一個線性串列,它可以在佇列任一端加入或刪除元素。設計一個表示法,使用一維陣列來表示雙端佇列,再寫出從任一端加入或刪除雙向佇列的演算法。 :::spoiler ```cpp= # include <stdio.h> # include <iostream> #define MAXSIZE 10 using namespace std; int queue[MAXSIZE]; int head = -1, tail = -1; void Insert(int v) { if(tail == MAXSIZE) cout << "overflow" << endl; else { if(head == -1 && tail == -1) { head = 0; tail = 0; } else tail++; queue[tail] = v; } } void f_Insert(int v) { if(tail == MAXSIZE) cout << "overflow" << endl; else { if(head == -1 && tail == -1) { head = 0; tail = 0; } else tail++; for(int i = tail; i>0; i--) { queue[i] = queue[i-1]; } queue[head] = v; } } int Pop() { if(head == -1 || head > tail) { cout << "No element" << endl; return -1; } else { int v = queue[head]; head++; if(head > tail) { head = -1; tail = -1; } return v; } } int b_Pop() { if(head == -1 || head > tail) { cout << "No element" << endl; return -1; } else { int v = queue[tail]; tail--; if(head > tail) { head = -1; tail = -1; } return v; } } void Display() { for(int i = head; i <= tail; i++) { cout << queue[i] << " "; } cout<<endl; } int main() { string s; int v; while(cout<<"前加入(j)/後加入(i)/前刪除(p)/後刪除(q) :",cin>>s) { if(s=="p")Pop(); if(s=="q")b_Pop(); if(s=="i") { cout<<"n :"; cin>>v; Insert(v); } if(s=="j") { cout<<"n :"; cin>>v; f_Insert(v); } if(head != -1)Display(); } } ``` ::: --- ### 5. 一個 m×p 的迷宮,全部有可能的路徑中,最長的長度為多少? :::spoiler 路徑最長的長度(即為最差的情況):$\text{max}( \frac{m}{2}×(p+1),\ + \frac{p}{2}×(m+1))$ ::: --- ### 7. 寫出下列式子的後置式: <br> &emsp; (a) A×B×C <br> &emsp; (b) -A+B-C+D <br> &emsp; ($\text{c}$) A×-B+C <br> &emsp; (d) (A+B)×D+E/(F+A×D)+C <br> &emsp; (e) A&&B\||C\||!(E>F) <br> &emsp; (f) !(A&&!((B<C)\||(C>D)))\||(C<E) <br> &emsp; (g) A and B or C or not (E>F) <br> &emsp; (h) (A+B)*C^(D+E*F-G*(H+J)) :::spoiler (a) AB ×C × (b) A − B + C − D + ($\text{c}$) AB − ×C + (d) AB + D× EFAD× + /+ C + (e) AB &&C \|| EF >!\|| (f) ABC < CD >\||!&&!CE <|| (g) ABandCorEF > notor (h) AB+CDEF*+GHJ+*-^* ::: --- ### 9. 另外一種容易計算且不必加括號式子的表示法就是前序表示,這種表示法就是將運算子放在運算元的前面,請見範例 3-13: <br> &emsp; (a) 將習題 6 表示成對應的前序表示。 <br> &emsp; (b) 寫一個演算法來計算前置表示法 e。 <br> &emsp; ($\text{c}$) 寫一個演算法把中置式 e 轉成相等的前置式,假設每個 e 都以符號 "#"做開頭,且其前置式的也要以 "#" 做開頭。 <br> 你的演算法 (b) 和 ($\text{c}$) 的複雜度為多少?每個演算法需要多少空間? :::spoiler (a) − − +12 /× /× 34567 / 89 (b) ```cpp= # include <iostream> # include <string> # include <algorithm> using namespace std; int top=0; int opndstk[100000]; int opndstk_pop() { if (top==-1) cout<<"empty stack."<<endl; else return opndstk[--top]; } int calculate(int o1, int o2, char opr) { if (opr == '+') return o1+o2; if (opr == '-') return o1-o2; if (opr == '*') return o1*o2; else return o1/o2; } int main() { int i=-1; string s; cin>>s; reverse(s.begin(), s.end()); int ans=0; int opnd1, opnd2; while (++i<s.size()) { if (isdigit(s[i])) opndstk[top++] = s[i]-'0'; else { opnd1=opndstk_pop(); opnd2=opndstk_pop(); opndstk[top++]=calculate(opnd1, opnd2, s[i]); cout<<opnd1<<" "<<s[i]<<" "<<opnd2<<" = "<<calculate(opnd1, opnd2, s[i])<<endl; } } cout<<"Ans = "<<opndstk[top]<<endl; } ``` ($\text{c}$) ```cpp= 輸入:中序運算式e 輸出:對應輸入之前序運算法 n = parsing(e, token); push(“#”); for (i=0; i<n; i++) { s = token(i); if (s是一運算元) push_opn(s); else if (s == ”)”) //將堆疊中第一個(之前的運算子皆pop並印出 { while((x=pop())!=”(”) push_opn(get_prefix(x)); } else { while ( p(s) <= q(Stack[top]) ) { x = pop(); push_opn(get_prefix(x)); } push(s); } } while (Stack[top]!=”#”) { x = pop(); push_opn(get_prefix(x) } x = pop(); // pop out “#” return pop_opn(); //—------------------ String get_prefix(x) { String a = pop_opn(); return x+pop_opn()+a; } ``` ::: --- ### 11. 有兩個堆疊用這節討論的方法儲存在陣列 M[m],寫一個演算法 Add 和Delete 在堆疊 i 中,0 ≤ i ≤ 1,做加入和刪除的動作,你的演算法必須保證只要兩個堆疊的元素總和小於 m 就能加入元素,而且執行時間必須在 O(1) 內。 :::spoiler 對 Stack0 做 &emsp; push : Stack0[++top] = element; &emsp; pop : return Stack[top--]; | | Stack0 | Stack1 | |:----- |:-------------------- |:------ | | Init. | top0 = -1 | top1 = m | | push | Stack0[++top] = data | Stack1[--top] = data | | pop | return Stack0[--top] | Stack1[top++] | | Full | if (top1==top0) | if (top0 == top1) | | Empty | if (top == -1) | if (top == m) | ![](https://i.imgur.com/5VYZ6Pw.png) ::: --- ### 13. 設計一個資料結構,將 $n$ 個資料物件連續對應到陣列 M[$m$],其中 $n_1$ 個物件為堆疊,剩下的 $n_2$ = $n−n_1$ 為佇列。寫出加入和刪除的演算法,只要陣列中還有空間沒使用到,此演算法就要提供空間給第 $i$ 個資料物件。注意:一個有 $r$ 個空間的環形佇列只能儲存 $r−1$ 個元素。 :::spoiler ```cpp= #define SIZE 6 #include <iostream> using namespace std; int Cqueue[SIZE]; int front = -1,rear = -1; bool QueueFull() { if (front == 0 && rear == SIZE - 1)return true; if (front == rear + 1)return true; return false; } bool isEmpty() { if (front == -1)return true; else return false; } //增加元素 void Add(int element) { if (QueueFull())cout << "Queue is full" << endl; else { if (front == -1) front = 0; rear = (rear + 1) % SIZE; Cqueue[rear] = element; cout << "Inserted :" << element << endl; } } //刪除元素 int Delete() { int element; if (isEmpty()) { cout << "Queue is empty" << endl; return -1; } else { element = Cqueue[front]; if (front == rear) { front = -1; rear = -1; } else front = (front + 1) % SIZE; return (element); } } int Spop(){ int element; if (isEmpty()) { cout << "Queue is empty" << endl; return -1; } else { element = Cqueue[rear]; if (rear == front) { front = -1; rear = -1; } else rear = (rear - 1) % SIZE; return (element); } } void display() { int i; if (isEmpty()) cout << endl << "Empty Queue" << endl; else { cout << "Front -> " << front; cout << endl << "Items -> "; for (i = front; i != rear; i = (i + 1) % SIZE)cout << Cqueue[i]; cout << Cqueue[i]; cout << endl << "Rear -> " << rear <<endl; } } int main(void) { int select ,temp; int loop = 1; while ( loop )//主迴路開始 { printf("[1]Add [2]Queue Pop [3]Stack Pop ==>\n"); cin >> select;//讀入選項 switch ( select ) { case 1: printf("input value(%d,%d) ==> ",front,rear); cin >> temp; Add(temp); display(); break; case 2: if (isEmpty()) cout << "No Data" << endl ; else { temp = Delete(); cout << "Queue Pop :" << temp << endl; } cout << "front -> " << front << endl; cout << "Rear -> " << rear << endl; break; case 3: if (isEmpty()) cout << "No Data" << endl ; else { temp = Spop(); cout << "Stack Pop :" << temp << endl; } cout << "front -> " << front << endl; cout << "Rear -> " << rear << endl; break; case 4: loop = 0; break; } } system("PAUSE"); return 0; } ``` ![](https://i.imgur.com/geczlSX.png) ::: --- --- ## 第四章 鏈結串列 --- ### 1. 設計一個演算法來計算一個由 first 指標指向開頭節點的單向鏈結串列所含的節點總數目,串列中最後一個節點的 link 欄為 NULL。計算此演算法的時間複雜度。 :::spoiler ```cpp= while (first!=null) { first = first->next; counter++; } ``` ```cpp= int CountNodes(struct node * first) { int cnt; for (cnt=0, p=first; p; cnt++, p=p->next); return cnt; } ``` 時間複雜度:*O*($n$),其中 $n$ 為串列節點個數。 ::: --- ### 3. 設計三個演算法分分別來計算: <br> &emsp; (a) 由 first 指標指向開頭節點的雙向鏈結串; <br> &emsp; (b) 由 first 指標指向開頭空節點的雙向鏈結串; <br> &emsp; ($\text{c}$) 由 p 指標指向其中一節點的雙向鏈結串;所含的非空節點總數目,串列中最後一個節點的 link 欄為 NULL。分析此三個演算法的時間複雜度。 :::spoiler (a) ```cpp= count = 0; while(first != NULL) { first = first->next; count++; } for (cnt=0, p=first; p; cnt++, p=p->link); ``` *O*($n$) (b) ```cpp= count = 0; while(first != NULL) { first = first->next; count++; } count--; int CountNodesHead(struct node * first) { int cnt; for (cnt=0, p=first->link; p; cnt++, p=p->link); return cnt; } ``` *O*($n$) (c\) ```cpp= count = 0; if(p != NULL) { q = p->pre; while(p != NULL) { p = p->next; count++; } while(q != NULL) { q = q->pre; count++; } } for (cnt=1, a=p->link; a; cnt++, a=a->link); for (b=p->prev; b; cnt++, b=b->prev); ``` 時間複雜度:*O*($n$) ::: --- ### 5. 令兩個鏈結串列分別為:$x = (x_1, x_2, ..., x_n)$ 與 $y = (y_1, y_2, ..., y_m)$,其穿插合併串列 z 定義成:若 m ≤ n 則 $z = (x_1, y_1, x_2, y_2, ..., x_m, y_m, x_{m+1}, x_{m+2}, ..., x_n)$ ,若 $m > n$ 則 $z = (x_1, y_1, x_2, y_2, ..., x_n, y_n, y_{n+1}, x_{n+2}, ..., y_m)$;而合併後,$x$ 與 $y$ 其原先每一節點現在都存於 $z $中,所以 $x$ 與 $y$ 將變為空串列。請設計演算法求得 $x$ 與 $y$ 的穿插合併串列 $z$;且合併過程中不能使用其他節點。請分別考慮: <br> &emsp; (a) 單向鏈結串列(含或不含開頭空節點); <br> &emsp; (b) 單向環狀鏈結串列; <br> &emsp; ($\text{c}$) 雙向環狀鏈結串列;並計算演算法的時間複雜度。 :::spoiler 令節點結構如下: ```cpp= struct node { int data; struct node * next; }; ``` 或 ```cpp= struct Dnode { struct Dnode * prev; int data; struct Dnode * next; }; struct node *first_x, *first_y, *first_z; // 前兩者分別指向串列 X, Y,而後者為空指標/開頭空白節點;或 struct Dnode *first_x, *first_y, *first_z; ``` (a) Singly linked list ```cpp= //不含開頭空節點 struct node * MergeList(struct node *first_x, struct node *first_y) { struct node *a, *b, *first_z, *last; // cnt_x = CountNodes(first_x); // CountNodes() 請參考習題 1 // cnt_y = CountNodes(first_y); if (!first_x) return first_z = first_y; if (!first_y) return first_z = first_x; first_z = first_x; a = first_z->next; first_z->next = first_y; b = first_y->next; first_z->next = b; for (last=first_z->next; a && b; ) { last->next = a; a = a->next; last->next->next = b; b = b->next; last = b; } last->next = (a) ? a : b; first_x = first_y = NULL; return first_z; } ``` ```cpp= // 含開頭空節點 struct node * MergeListHead(struct node *first_x, struct node *first_y, struct node *first_z) // Note: *first_z has been created in main() { struct node *a, *b, *last; if (!first_x->next) return first_z = first_y; if (!first_y->next) return first_z = first_x; for (a=first_x->next, b=first_y->next, last=first_z; a && b; ) { last->next = a; a = a->next; last->next->next = b; b = b->next; last = b; } last->next = (a) ? a : b; first_x->next = first_y = NULL; return first_z; } ``` 時間複雜度:*O*($m+n$) (b) Singly cyclic linked list ```cpp= //不含開頭空節點 struct node * MergeList(struct node *first_x, struct node *first_y) { struct node *a, *b, *p, *first_z, *last; if (!first_x) return first_z = first_y; // if X is empty if (!first_y) return first_z = first_x; // if Y is empty first_z = first_x; a = first_x->next; first_z->next = first_y; b = first_y->next; for (last=first_z->next; a!=first_x && b!=first_y; ) { last->next = a; a = a->next; last->next->next = b; b = b->next; last = b; } last->next = (a) ? a : b; for (p=last->next; p->next!=first_x && p->next!=first_y; p=p->next); p->next = first_z; first_x = first_y = NULL; return first_z; } ``` ```cpp= // 含開頭空節點 struct node * MergeListHead(struct node *first_x, struct node *first_y, struct node *first_z) // Note: *first_z has been created in main() { struct node *a, *b, *first_z, *last; if (first_x->next==first_x) return first_z = first_y; if (first_y->next==first_y) return first_z = first_x; for (a=first_x->next, b=first_y->next, last=first_z; a && b; ) { last->next = a; a = a->next; last->next->next = b; b = b->next; last = b; } last->next = (a) ? a : b; for (p=last->next; !p->next!=first_x && p->next!=first_y; p=p->next); p->next = first_z first_x->next = first_x; first_y->next = first_y; return first_z; } ``` 時間複雜度:*O*($m+n$) (c) Doubly cyclic linked list ```cpp= //不含開頭空節點 struct Dnode * MergeList(struct Dnode *first_x, struct Dnode *first_y) { struct Dnode *a, *b, *p, *first_z, *last; if (!first_x) return first_z = first_y; // if X is empty if (!first_y) return first_z = first_x; // if Y is empty first_z = first_x; a = first_x->next; first_z->next = first_y; b = first_y->next; first_z->next->prev = first_z; for (last=first_z->next; a!=first_x && b!=first_y; ) { last->next = a; a->prev = last; //last->next->prev = last; a->next = b; b->prev = a; //last->next->prev = last->next; a = a->next; b = b->next; last = b; } last->next = (a) ? a : b; for (p=last->next; p->next!=first_x && p->next!=first_y; p=p->next); p->next = first_z; first_z->prev = p; first_x = first_y = NULL; return first_z; } ``` ```cpp= // 含開頭空節點 struct Dnode * MergeListHead(struct Dnode *first_x, struct Dnode *first_y, struct Dnode *first_z) // Note: *first_z has been created in main() { struct Dnode *a, *b, *first_z, *last; if (first_x->next==first_x) return first_z = first_y; if (first_y->next==first_y) return first_z = first_x; for (a=first_x->next, b=first_y->next, last=first_z; a && b; ) { last->next = a; a->prev = last; a->next = b; b->prev = a; a = a->next; b = b->next; last = b; } last->next = (a) ? a : b; for (p=last->next; !p->next!=first_x && p->next!=first_y; p=p->next); p->next = first_z; first_z->prev = p; first_x->prev = first_x->next = first_x; first_y->prev = first_y->next = first_y return first_z; } ``` 時間複雜度:*O*($m+n$) ::: --- ### 7. 令 p 為指向環形鏈結串列的一指標,如何將此串列當作一佇列使用?(亦即寫出其新增與刪除元素的程序)。 :::spoiler ```cpp= struct node { int data; struct node *next; };//使 p 指向環狀串列任一個當開頭,令 q 指向 p 的前一個當環狀串列的結尾 struct node * p,*q; q = p->next; while(q->next != p) q = q->next; int delete()//delete:將p 指到下一個即可刪除原本 p 所指的元素 { r = p; p = p->next; return r->data; } void add( v ) //add:加到 q 後面 { struct node *s; s->data = v; s->next = NULL; q->next = s; q = q->next; } ``` ::: --- ### 9. 令 A 和 B 皆為多項式, <br> &emsp; $A(x) = \sum\limits_{i=0}^{n}{a_ix^i}, B(x) = \sum\limits_{i=0}^{n}{b_ix^i}$ <br> 多項式相加的定義為: <br> &emsp; $A(x) + B(x) = \sum\limits_{i=0}^{n}{(a_i+b_i)x^i}$ <br> 請參考 3.6.1 節用鏈結串列表示多項式,設計出輸入多項式的程序,並與程式 4-17(多項式相加的程序)搭配,實作兩輸入多項式的相加,並輸出其結果。請分析並驗証使用演算法的時間與空間複雜度。 <br> 提示:可將節點設計成可儲存:非 0 係數 $c_i$、其對應冪次 $i$ 與下一非 0 項指標的節點。注意在多項式的 $n$ 很大而非 0 項數很少時,鏈結串列的表示可節省空間。 :::spoiler 繼第8題 ```cpp= //Polynomial addition Term Add(Term a, Term b) //a and b are descending polynomial { Term result = NewTerm(-1, -1), term = result; a = a->next; b = b->next; //Skip blank nodes while (a && b) //If a and b are not empty, enter the loop { if (a->pow > b->pow) { term->next = NewTerm(a->coef, a->pow); a = a->next; term = term->next; } else if (a->pow < b->pow) { term->next = NewTerm(b->coef, b->pow); b = b->next; term = term->next; } else { if (a->coef - b->coef) term = term->next = NewTerm(a->coef + b->coef, a->pow); a = a->next; b = b->next; } } for (a = a ? a : b; a; a = a->next, term = term->next) term->next = NewTerm(a->coef, a->pow); return result; } ``` /* --- 執行結果 5 5 6 1 7 3 3 5 6 3 Ans : 8x^5 + 13x^3 + 6x^1 1 2 3 7 6 6 5 2 5 3 Ans : 3x^7 + 6x^6 + 5x^3 + 6x^2 57 6 7 3 32 5 -12 1 33 5 -12 3 -8 0 Ans : 57x^6 + 65x^5 - 5x^3 - 12x^1 - 8 --- */ ![](https://i.imgur.com/03PVoNp.png) 時間複雜度:*O*($n+m$),其中 $n$ 是多項式 A 中的項數,$m$ 是多項式 B 中的項數。 ::: --- ### 11. 仿照 8、9 和 10 的題意,設計出多項式相減和相除的演算法。請分析所用演算法的時間與空間複雜度。 :::spoiler ```cpp= //Polynomial subtraction Term Sub(Term a, Term b) //a and b are descending polynomial { Term result = NewTerm(-1, -1), term = result; a = a->next; b = b->next; //Skip blank nodes while (a && b) //If a and b are not empty, enter the loop { if (a->pow > b->pow) { term->next = NewTerm(a->coef, a->pow); a = a->next; term = term->next; } else if (a->pow < b->pow) { term->next = NewTerm(-b->coef, b->pow); b = b->next; term = term->next; } else { if (a->coef - b->coef) term = term->next = NewTerm(a->coef - b->coef, a->pow); a = a->next; b = b->next; } } int pn = a ? 1 :-1; for (a = a ? a : b; a; a = a->next, term = term->next) term->next = NewTerm(a->coef * pn, a->pow); return result; } ``` /* --- 執行結果 5 5 6 1 7 3 3 5 6 3 Ans : 2x^5 + 1x^3 + 6x^1 1 2 3 7 6 6 5 2 5 3 Ans : 3x^7 + 6x^6 - 5x^3 - 4x^2 57 6 7 3 32 5 -12 1 33 5 -12 3 -8 0 Ans : 57x^6 - 1x^5 + 19x^3 - 12x^1 + 8 --- */ ![](https://i.imgur.com/G3vXPNQ.png) 時間複雜度:*O*($n+m$),其中 $n$ 是多項式 A 中的項數,$m$ 是多項式 B 中的項數。 ```cpp= //Polynomial division #include <stdio.h> //#include <stdlib.h> //CodeBlock會跟內建函式衝到 #define swap(type, a, b) { type t = a; a = b, b = t; } typedef struct Fraction //Fraction { int num, den; //Numerator and Denominator } Frac; typedef struct NodeOfPolynomial { struct Fraction coef; //Coefficient int pow; //Power struct NodeOfPolynomial *next; //Next term } *Term; // Functions of Fraction inline Frac Red(struct Fraction frac) //Reduction of fraction { int a = abs(frac.num), b = abs(frac.den), c; while (b) c = b, b = a%b, a = c; //GCD return (Frac){frac.num / a, frac.den / a}; } inline struct Fraction add(Frac a, Frac b) //Addition { return Red((Frac){a.num*b.den+b.num*a.den, a.den*b.den}); } inline struct Fraction sub(Frac a, Frac b) //Subtraction { return Red((Frac){a.num*b.den-b.num*a.den, a.den*b.den}); } inline struct Fraction mul(Frac a, Frac b) //Multiplication { return Red((struct Fraction){a.num*b.num, a.den*b.den}); } inline struct Fraction div(Frac a, Frac b) //Division { return Red((struct Fraction){a.num*b.den, b.num*a.den}); } inline void output(struct Fraction frac) { if (frac.den == 1) printf("%d", abs(frac.num)); else printf("(%d/%d)", abs(frac.num), frac.den); } // Functions of Polynomial Term NewTerm(struct Fraction ceof, int pow) { Term term = (Term)malloc(sizeof(struct NodeOfPolynomial)); term->coef = ceof; term->pow = pow; term->next = NULL; return term; } void Sub(Term A, Term B) //Polynomial subtraction { Term pre = A, a = A->next, b = B->next, next; free(B); while (a && b) //If a and b are not empty, enter the loop { if (a->pow > b->pow) { pre = a; a = a->next; } else if (a->pow < b->pow) { b->coef.num = -b->coef.num; pre->next = b; pre = b; next = b->next; b->next = a; b = next; } else { a->coef = sub(a->coef, b->coef); if (a->coef.num) pre = a = a->next; else pre->next = a->next, free(a), a = pre->next; next = b->next; free(b); b = next; } } if (b) pre->next = b; } Term *Div(Term a, Term b) //Polynomial division { Term result = NewTerm((struct Fraction){-1, 1}, -1), t, term = result, tmp = NewTerm((Frac){-1, 1}, -1); for (a=a->next, t=tmp; a; a=a->next, t=t->next) t->next = NewTerm(a->coef, a->pow); a = tmp; while (a->next->pow >= b->next->pow) { Term mcl = NewTerm((Frac){-1, 1}, -1), m = mcl; term = term->next = NewTerm(div(a->next->coef, b->next->coef), a->next->pow - b->next->pow); for (Term bi = b->next; bi; bi = bi->next, m = m->next) m->next = NewTerm(mul(bi->coef, term->coef), bi->pow+term->pow); Sub(a, mcl); } Term *ans = (Term *)malloc(sizeof(Term)*2); ans[0] = result; //Quotient ans[1] = a; //Remainder return ans; } inline void Clear(Term pol) { for (Term next; pol; next = pol->next, free(pol), pol = next); } void Output(Term polynomial) { int i = 0; for (Term term = polynomial->next; term; term = term->next, ++i) { printf(term->coef.num > 0 ? " + "+(!i<<1) : " - "); output(term->coef); if (term->pow) printf("x^%d", term->pow); } puts(""); } void Sort(int *cbegin, int *cend, int *pbegin, int *pend) //Quick Sort { if (pbegin < pend-1) { int split = *pbegin, *i = pbegin, *ci = cbegin; for (int *j = pbegin+1, *cj = cbegin+1; j != pend; ++j, ++cj) { if (*j > split) { ++i; ++ci; swap(int, *i, *j); swap(int, *ci, *cj); } } swap(int, *i, *pbegin); swap(int, *ci, *cbegin); Sort(cbegin, ci, pbegin, i); Sort(ci + 1, cend, i + 1, pend); } } int main() { int coefs1[100], pows1[100], size1 = 1; int coefs2[100], pows2[100], size2 = 1; //Input coefficients and powers for (int *coef = coefs1, *pow = pows1; scanf("%d %d", coef, pow) != EOF && getchar() != '\n'; ++coef, ++pow, ++size1); for (int *coef = coefs2, *pow = pows2; scanf("%d %d", coef, pow) != EOF && getchar() != '\n'; ++coef, ++pow, ++size2); //Sorting to achieve descending power polynomials Sort(coefs1, coefs1 + size1, pows1, pows1 + size1); Sort(coefs2, coefs2 + size2, pows2, pows2 + size2); Term polynomial1 = NewTerm((struct Fraction){-1, 1}, -1), term1 = polynomial1, polynomial2 = NewTerm((struct Fraction){-1, 1}, -1), term2 = polynomial2; for (int i = 0; i < size1; ++i, term1 = term1->next) term1->next = NewTerm((Frac){coefs1[i], 1}, pows1[i]); for (int i = 0; i < size2; ++i, term2 = term2->next) term2->next = NewTerm((Frac){coefs2[i], 1}, pows2[i]); Term *polynomial3 = Div(polynomial1, polynomial2); printf("Quotient :"); Output(polynomial3[0]); printf("Remainder:"); Output(polynomial3[1]); Clear(polynomial3[0]); Clear(polynomial3[1]); free(polynomial3); return 0; } ``` /* --- 執行結果 2 2 3 1 1 0 1 1 3 0 Quotient : 2x^1 - 3 Remainder: 10 3 2 2 1 1 0 2 1 3 0 Quotient : (3/2)x^1 - (5/4) Remainder: (19/4) 2 5 1 4 -2 1 1 0 5 2 4 1 Quotient : (2/5)x^3 - (3/25)x^2 + (12/125)x^1 - (48/625) Remainder: - (1058/625)x^1 + 1 --- */ ![](https://i.imgur.com/3ICfAVp.png) 時間複雜度:*O*($max(nm, n(em-en))$),其中 $m$ 是多項式 A 中的項數,$n$ 是多項式 B 中的項數,$em$ 是多項式 A 的最大冪,$en$ 是多項式 B 的最大冪。 ::: --- ### 13. 利用環形串列重做習題 7~10。請比較兩做法的時間複雜度。 :::spoiler &emsp; 一般而言,線性串列、環狀串列、含開頭空白節點的環狀串列、或雙向串列在搜尋、新增(至某節點後)、刪除... 皆有相同的時間複雜度(即令處理的細節互有所異);唯在邊界條件 (first?=NULL、head?=NULL、...)判定時,含開頭空白節點的環狀串列可省下檢測是否為空串列的考量。 ::: --- ### 15. 如節 4.7 所介紹:當矩陣的非 0 元素很多時,稱此矩陣為稀疏矩陣 (sparse matrix)。請設計儲存非 0 元素的節點結構。若所設計的結構與圖 4-31 不同,請比較並討論其優劣。 :::spoiler ```cpp= typedef struct Matrix_Node { int data; struct Pos { int i, j; } pos; struct Matrix_Node *next_r, *next_c; } *Node; ``` ::: --- ### 17. 請設計演算法,解決兩稀疏矩陣的相加。請分析演算法的複雜度。 :::spoiler ```cpp= void Mul(Sparse_Matrix a, Sparse_Matrix b, int m, int p, int q) { Initialization(&result, m, q); for (int i = 0; i < m; i++) { for (int j = 0, n; j < q; j++) { Node ap = a.row_f[i], bp = b.col_f[j], x; n = 0; while (ap && bp) { if (ap->pos.j < bp->pos.i) ap = ap->next_r; else if (ap->pos.j > bp->pos.i) bp = bp->next_c; else { n += ap->data * bp->data; ap = ap->next_r; bp = bp->next_c; } } if (n) { x = NewNode(n, i, j); InsertLastR(&result.row_f[i], &result.row_l[i], x); InsertLastC(&result.col_f[j], &result.col_l[j], x); } } } } int main() { int m, p, q; scanf("%d %d %d", &m, &p, &q); Build(m, p, q); Mul(a, b, m, p, q); printf("\nResult:"); Output(result, m, q); } ``` /* --- 執行結果 5 5 5 0 0 3 0 6 0 7 0 0 0 5 3 0 0 0 0 0 0 5 0 0 7 0 0 0 0 0 5 0 0 0 2 0 0 8 3 0 0 0 2 0 0 0 2 1 0 0 3 0 0 Result: 9 0 18 0 6 0 14 0 0 56 0 6 25 0 24 0 0 0 10 5 0 14 0 0 56 --- */ ![](https://i.imgur.com/3HCZJFi.png) 時間複雜度:*O*($nm + pq$),其中 $n$ 是稀疏矩陣 A 中非零元素的數量,$m$ 是稀疏矩陣 B 中非零元素的數量,$p$ 是稀疏矩陣 A 中的行數,$q$ 是稀疏矩陣 B 中的列數 。 ::: --- --- ## 第五章 樹 --- ### 1. 針對程式 5-1 一般化串列鏈結節點的宣告,設計樹的輸入,寫出必要的程序,使能完成樹的一般化鏈結串列的建構。 :::spoiler ```cpp= Add (struct TreeNode *p, struct TreeNode *subtree) { struct TreeNode *s; s->tag = 1; s->link = p->link; p->link = s; s->node = subtree; } ``` ::: --- ### 3. 在5.2.3節中我們討論了分支度為2的樹表示法,請寫出此節點的程式宣告。 :::spoiler ```cpp= # include <stdio.h> struct node { struct node *Left; int data; struct node *Right; }; struct node *root; int main(){ return 0; } ``` ::: --- ### 5. 參考程式 5-6-1,實作出非遞迴版本的程式,執行二元樹前序走訪。 :::spoiler ```cpp= # include <stdio.h> # include <stdlib.h> //BinaryTreeNode struct node { struct node *LeftChild; int data; struct node *RightChild; }; struct node *NewNode(int x) { struct node *Node = (struct Node *)malloc(sizeof(struct node)); Node->data = x; Node->LeftChild = NULL; Node->RightChild = NULL; return Node; } struct node *Insert(struct node *Node,int x){ if (Node == NULL) return NewNode(x); if (x < Node->data) Node->LeftChild = Insert(Node->LeftChild, x); else Node->RightChild = Insert(Node->RightChild, x); return Node; } struct node *root; //Stack struct node **Stack; int top = -1; void push(struct node *element) { if (top >= 1000) return; else Stack[++top] = element; } struct node *pop(){ if (top == -1)return NULL; else return Stack[top--]; } int StackEmpty() { return top == 1000; } //preorder void preorder() { struct node *p = root; while (!StackEmpty()||p) { while (p) { printf("%d\n", p->data); push(p); p = p->LeftChild; } if (!StackEmpty()) { p = pop(); p = p->RightChild; } } } int main() { Stack = (struct Node **)malloc(1000 * sizeof(struct node*));//Insert 100 random number(0~32767) for(int i=0; i<100; i++) { int number = rand(); root = Insert(root,number); } preorder(); } ``` ::: --- ### 7. 參考程式 5-7 實作出階層走訪 (level-order travesal) 的程式,走訪一二元樹。 :::spoiler ```cpp= # include <stdio.h> # include <stdlib.h> //BinaryTreeNode struct node { struct node *LeftChild; int data; struct node *RightChild; }; struct node *NewNode(int x) { struct node *Node = (struct Node *)malloc(sizeof(struct node)); Node->data = x; Node->LeftChild = NULL; Node->RightChild = NULL; return Node; } struct node *Insert(struct node *Node,int x){ if (Node == NULL) return NewNode(x); if (x < Node->data) Node->LeftChild = Insert(Node->LeftChild, x); else Node->RightChild = Insert(Node->RightChild, x); return Node; } struct node *root; //Queue struct node **Queue; int front = -1, rear = -1; void AddQueue(struct node *element) { if (front >= 1000) return; else Queue[++rear] = element; } struct node *DeleteQueue(){ if (rear == front)return NULL; else return Queue[++front]; } int QueueEmpty() { return front == rear; } //levelOrder void LevelOrder() { AddQueue(root); struct node *p = NULL; while (!QueueEmpty()) { p = DeleteQueue(); printf("%d\n", p->data); if (p->LeftChild ) AddQueue(p->LeftChild); if (p->RightChild) AddQueue(p->RightChild); } } int main() { Queue = (struct Node **)malloc(1000 * sizeof(struct node*)); //Insert 100 random number(0~32767) for(int i=0; i<100; i++) { int number = rand(); root = Insert(root,number); } LevelOrder(); } ``` ::: --- ### 9. 寫出一演算法將輸入的二元樹 T,以 SwapTree(T)交換每一個節點的左右子點。請看圖 5-55 的例子。 ![](https://i.imgur.com/h5jpP6O.png) :::spoiler ```cpp= SwapTree(Tree T) { for( T 上每一個點) { node = T->leftchild; T->leftchild = T->rightchild; T->rightchild = node; } } ``` ::: --- ### 11. 請參考程式 5-12,寫出程序—使可插入節點 new 成為引線二元樹的節點 p 的左子節點。 :::spoiler ```cpp= void Threaded::InsertLeft ( ThreadedNode *s, char ch)//建立node h; 將ch 存入h; 插入h 作為s 的左兒子 { ThreadedNode *h = new ThreadNode(ch); h->LeftChild = s->LeftChild; h->LeftThread = s->LeftThread; h->RightChild = s; h->RightThread = TRUE; // 左兒子為引線 s->LeftChild = h; //把h 加到s s->LeftThread = FALSE; if(!LeftThread) { ThreadedNode *temp = InorderPred(1); Temp->RightCgild = 1; } } ``` ::: --- ### 13. 寫一演算法以在最小堆積中作插入動作。這個演算法的複雜度應為O(logn),請證明。 :::spoiler ```cpp= void InsertHeap(int x) { if (n == maxsize) HeapFull(); else { n++; i = n; while ((i > 1) && (x < heap[i/2])) { heap[i] = heap[i/2]; i /= 2; } heap[i] = x; } } ``` 新增節點的位置若在 k 處,則新增資料可能的位置,會在 k 往上走至樹根的任何節點上,亦即最差情況程式迴圈會執行 ⎡log(k+1)⎤ 次,這也是此完備二元樹的深度。因此程序 InsertHeap 在插入第 $n$ 個資料時,需要的時間複雜度為 *O*($logn$)。 ::: --- ### 15. 證明一樹林的前序走訪與其對應二元樹的前序走訪,結果相同。 :::spoiler 假設$(T_1, T_2, ..., T_n)$視為一樹林且$PO(T_1, T_2, ..., T_n)$為樹林的前序走訪,將森林的樹個別做前序走訪並依序合併寫作$PO(T_1), PO(T_2), ..., PO(T_n)$,可將$T_1$視為是跟以及左子樹,$T_2, T_3, ..., T_n$視為右子樹,顯然的,$T_1$ 在其他樹的元素前先做了前序走訪,重複這個假設,我們可以得到 $T_1, T_2, ..., T_n$ 的前序走訪會與樹林的前序走訪有相同的順序。 ::: --- ### 17. 以樹林後序走訪法,寫一個非遞迴程序走訪一樹林的對應二元樹。並計算你的程序之時間與空間複雜度。 :::spoiler ```cpp= Void Forest :: postorder() { Stack<ForrstNode*> s; Stack<int> t; int CurrentFlag; ForestNode *CurrentNode = root; while (1) { while (CurrentNode) { s.Add(CurrentNode);//存此點,之後會走其右子樹 t.Add(0);//左右子樹需要被走訪 CurrentNode = CurrentNode->Leftchild;//走訪左子樹 if(!s.Empty () { CurrentNode = *s.Delete (CurrentNode); CurrentFlag = *t.***Dr1rte (CurrentFlsg); if(CurrentFlag==0)//右子樹尚未走訪 { if(CurrentNode !=root) cout<<CurrentNode->data<<end1; s.Add (CurrentNode) ; t.Add(1);//右子樹需要被走訪 CurrentNode = CurrentNode->Rightchild;//走訪右子樹 } else CurrentNode =0;// CurrentFlag ==1,左右子樹皆走訪過了 } } count<<root->data<<endl; } } ``` 時間複雜度 *O*($n$),$n$ 為樹林中節點的個數。 空間複雜度 *O*($n$),$n$ 為樹林中節點的個數。 ::: --- ### 19. 二元樹中的中序和前序順序,可否決定唯一的二元樹?說明或證明之。 :::spoiler 基礎:只有一個節點的樹,其前序與中序相同,因此二元樹是唯一的。 假設:假設 $n$ 為一個大於一的正整數,當樹的節點數少於 $n$ 時,由前序與中序可以得到唯一的二元樹。 推論:使 T 為一個有 $n$ 個節點的樹,使它的前序與中序分別為 $p_1, p_2, ..., p_n$ 和 $i_1, i_2, ...i_n$ ,前序的第一個元素為樹根,也就是 $p_1$,找到 $p_1$ 在中序中的位置,稱之為 $i_k$ ,因此,$(i_1, i_2, ..., i_{k-1})$ 與 $(p_2, p_3, ..., p_{k-1})$ 為T的左子樹,$(i_{k+1}, i_{k+2}, ..., i_n)$ 與 $(p_k, p_{k+1}, ..., p_{n-1})$ 為 T 的右子樹。 T 的左子樹與右子樹的節點數都少於 $n$,符合假設,也就代表其左子樹與右子樹的前序與中序可以得到唯一的二元樹,因為樹根為唯一的,且左右子樹也為唯一,因此 T 也為唯一的二元樹。 ::: --- ### 21. 寫一個演算法,以所提供的前序和中序順序,建立出對應的二元樹。 :::spoiler ```cpp= struct BTreeNode * BTree_InfixPrefix ( String infix , String prefix ) { int k; struct BTreeNode * node; if (infix.Length == 0) return NULL; node = (struct BTreeNode *) malloc (sizeof(struct BTreeNode)); node->data = prefix[1]; k = find(prefix[1], infix); node->leftchild = BTree_InfixPrefix (infix[1,k-1], prefix[2,k-1]); node->rightchild = BTree_InfixPrefix (infix[k+1,infix.Length-k],prefix[k+1,prefix.Length-k]); return node; } ``` ::: --- ### 23. 實作程式 5-23 新增資料至 AVL 樹中。 :::spoiler 程式 5-23 已編譯正確。 ::: --- --- ## 第六章 圖形 --- ### 1. 證明當我們對一個無向連通圖形 G 執行深先搜尋 DFS(演算法 6-1)時,T 中的所有邊會形成一棵樹。 &emsp;首先證明 H 是 connected,若 H 不是 connected,令 x 為 dfs 程序結束後,尚 未被標示為 true 的點,既然 G 為 connected,那麼一定存在至少一點連接標示為 true 的某一點和 x 內的某一點,這是矛盾的!(當一點被走訪,所有與其連接的點皆會被標示為 true)所以 H 是 connected。 &emsp;接下來我們得證明 H 中沒有 cycle,一開始{ H=ψ,每當一條 edge(u,v)要加 入 H 時,我們一定是找 visited[v]=false 者,然後設 visited[v]=true,假設(u,v)即為 令 H 造成 cycle 的 edge,表示當初 visited[u]和 visited[v]皆為 ture,既然兩者皆為 true,我們不可能考慮(u,v)的加入,所以 H 不會有 cycle。 --- ### 3. 假設 G 是一個無向連通圖形。證明 G 裡的邊沒有重複出現在 G 的兩個雙向連通成分 (biconnected component) 裡。 假設 G 裡的邊 (u, v) 重複出現在 2 個雙向連通成分 $C_1$ 和 $C_2$ 中,那麼 $C_1$ 的點可經由 (u, v) 雙向地往返 $C_2$ 的點,反之亦然;於是可將 $C_1$ 和 C2 組成更大的雙向連通成分—這與「雙向連通成分已是最大子圖」的限制產生矛盾!故 G 裡的邊不會重複出現在 2 個或以上的連通成分。 --- ### 5. 把 Kruskal 最小成本延展樹的演算法寫成完整的程式。 ```cpp= # include <iostream> #include<algorithm> using namespace std; struct edge { char v1, v2; int cost; }; edge e[10000];s int sorted[10000]; void SORT(int ary[], int n) { for (int k=0; k<n; k++) { int index = -1, MIN = 99999; for (int i=0; i<n; i++) { if (MIN > ary[i]) { index = i; MIN = ary[i]; } } sorted[k] = index; ary[index] = 99999; } } int Find(int parent[], int n) { if (parent[i] == -1) return i; return Find(parent, parent[n]); } void Union(int parent[], int x, int y) { int setX = Find(parent, x); int setY = Find(parent, y); parent[setX] = setY; } bool isCycle(edge e, int n, int m) { int parent = new int[n]; for (int i=0; i<n; i++) parent[i] = -1; for (int i=0; i<m; i++) { int x = Find(parent, e[sorted[i]].v1); int y = Find(parent, e[sorted[i]].v2); if (x == y) return 1; Union(parent, x, y); } return 0; } int main() { int n, m; int aryCost[10000]; cin>>n>>m; for (int i=0; i<n; i++) aryCost[i] = e[i].cost; SORT(aryCost, n); int k = 0; while(m < n-1) { edge next_edge = e[sorted[k++]]; } } ``` --- ### 7. 證明 Sollin 演算法可以為所有無向連通圖形建立起最小成本延展樹。 分成多個區域,而每個區域皆以 prim 來記錄,最後加入新的邊其兩端點必位於兩個區域中,並將兩個區域合併,故產生的樹為最小成本延展樹。 --- ### 9. 利用最短路徑(演算法 6-7、6-7-1)的概念,設計一個找出最小成本延展樹的演算法,其最糟狀況所花去時間為 *O*($n^2$)。 ```cpp= if (dist[u] + length[u][w] < dist[w]) { dist[w] = dist[u] + length[u][w]; path[w] = u; // u is currently the previous vertex //on shortest path to w } ``` --- ### 11. 實作任意兩點間最短路徑的程式。 ```cpp= # include <iostream> using namespace std; int main () { int n,w[100][100],A[100][100],a,b; cin>>n; for(int i=0;i<n;i++) { for(int j=0;j<n;j++) cin>>w[i][j]; } for(int i=0;i<n;i++) { for(int j=0;j<n;j++) A[i][j] = w[i][j]; } for(int k=0;k<n;k++) { for(int i=0;i<n;i++) { for(int j=0;j<n;j++) if(A[i][k]+A[k][j]<A[i][j] && i!=j) A[i][j] = A[k][j]+A[i][k]; } } cin>>a>>b; cout<<A[a][b]; } ``` ![](https://i.imgur.com/HLdt2F1.png) --- ### 13. 給定一個 n 個頂點的完全連通圖形,證明任意兩點間可能擁有的最多路徑數目是 *O*(($n−1$)!)。 令 G = (V, E) 為完備圖 (complete graph),n = |V|。既是任意兩點,不失一般性,令為點 1 和 2—考量其間的路徑個數。包含邊數為 1 的路徑只有一個(即 (1, 2));而包含兩條邊者(起點 1 終點 2 已固定;點 1 不能連到點 2)有 n-2 個可能(每個可能皆可有邊連到點2);包含三條邊者,有 (n-2)(n-3) 個可能;依此類推,包含 n-1 條邊者,有 (n-2)! 個可能。整理如下表: | 邊數 | 1 | 2 | 3 | ... | n-3 | n-2 | n-1 | |-----|---|---|---|-----|-----|-----|-----| | 可能路徑數 | 1 | n-2 | (n-2)(n-3) | ... | (n-2)(n-3)...3 | (n-2)(n-3)...2 | (n-2)(n-3)...1 | | 整理 | $\frac{(n-2)!}{(n-2)!}$ | $\frac{(n-2)!}{(n-3)!}$ | $\frac{(n-2)!}{(n-4)!}$ | ... | $\frac{(n-2)!}{2!}$ | $\frac{(n-2)!}{1!}$ | $\frac{(n-2)!}{0!}$ | 所有路徑個數為: $(n-2)! + \frac{(n-2)!}{1!} + \frac{(n-2)!}{2!} + … + \frac{(n-2)!}{(n-3)!} + 1 = (n-2)! + (n-2)! [1 + \frac{1}{2!} + \frac{1}{3!} + … + \frac{1}{(n-2)!}]$ 當 $n$ 趨近於無限大時,$[1 + 1/2! + 1/3!+ … + 1/n!] = e – 1$,而 $e = 2.718$。 於是所有路徑個數為 $c (n-2)! (< (n-1)!)$,其中 c 為常數,故任意兩點間可能擁有的最多路徑數目是 *O*$((n-1)!)$ --- ### 15. 設計一個演算法判斷圖形 G 是否為二分圖形,如果 G 是二分圖形則你的演算法應該要傳回該圖的兩個二分集合。證明如果以相鄰串列表示圓形,這個演算法可以在 *O*($n+e$) 的時間內完成,其中 n 是頂點數, e 是邊數。 input: (1) 以任意一點為起點,令此點為 0 (2) 將此點所有連接之點設為 1 (3) 檢查為 1 的點所連接之點,若為 1 則非雙分圖;否則令為 0 (4) 檢查為 0 的點所連接之點,若為 0 則非雙分圖;否則令為 1 (5) 重複(3)(4)若所有點都檢查過且都符合條件,則為雙分圖 建立一個矩陣 A 存放相鄰串列 建立一個矩陣 label 存放群組組別(0 或 1) ```cpp= input: G = (V, E) output: G 是否為二分圖形 select x∊E X = {x}; Y = V-X label[x] = 0; while ( X ∪ Y ≠ V ) { for ( each x∊X ) for ( each y∊Y where (x, y)∊E and label[y] is undefined) { label[y] = 1; Y = Y ∪ {y}; } if (no such y) break; for (each y∊Y) for (each z∊(V-Y-X) where (y, z)∊E and label[z] is undefined) { label[z] = 0; X = X ∪ {z}; } if (no such z) break; } return (X ∪ Y == V) ? 1 : 0; // 1:G is bipartite; 0:G is NOT ``` --- ### 17. 給定一個無向連通圖形 G,所有邊長皆為 1,設計一個函式利用廣先搜尋找出 G 的延展樹。 ```cpp= struct spNode // Adjacent list for spanning tree T { int u; int v; }; strut spList { struct spNode * link; }; struct spList T[n]; struct edgeNode // Adjacent list for input graph G { int u; int v; struct edgeNode * next; }; struct vertexList { struct edgeNode * edge; }; struct vertexList G[n]; void addAsFirst(struct spNode * first, struct spNode * q) { q->next = first->next; first->next = q; } struct spNode * newSPNode(edgeNode * p) { struct spNode * q = new struct spNode; q->u = p->u; q->v = p->v; return q; } void BFSSP(int u) // 以 u 為起點做 BFS { int v, i, visited[SIZE]; struct spNode * q; for (i=0; i<n; i++) visited[i] = 0; visited[u] = 1; T[u].link = NULL; Add_Queue(u); while (Queue 仍有頂點資料) { v = Delete_Queue(); for (p=G[v].edge; p; p=p->next) // 所有與 v 相鄰的頂點 w) { if (visited[p->v] == 0) { visited(p->v) = 1; q = newSPNode(p); addAsFirst(T[u].link, q); Add_Queue(p->v); } } } // T[u].edge 所指串列即為 “以 u 為起點做 BFS 後延展樹邊” 的集合; } ``` --- ### 19. 一棵樹的「半徑」(radius) 是指從根節點到其中某個葉 (leaf) 的最長長度。給定一個無向連通圖形 G,所有邊長皆為 1,設計一個函式找出 G 中有最小半徑的延展樹。 ```cpp= struct spNode // Adjacent list for spanning tree T { int u, v; int level; }; strut spList { int radius; struct spNode * link; }; struct spList T[n]; struct edgeNode // Adjacent list for input graph G { int u, v; struct edgeNode * next; }; struct vertexList { struct edgeNode * edge; }; struct vertexList G[n]; void addAsFirst(struct spNode * first, struct spNode * q) { q->next = first->next; first->next = q; } struct spNode * newSPNodeLevel(edgeNode * p, int level) { struct spNode * q = new struct spNode; q->u = p->u; q->v = p->v; q->level = level+1; return q; } int radiusBFSSP(int u) // 以 u 為起點做 BFS { int v, i, visited[SIZE], level; struct spNode * q; for (i=0; i<n; i++) visited[i] = 0; visited[u] = 1; T[u].radius = 0; T[u].link = NULL; Add_Queue(u, 0); while (Queue 仍有頂點資料) { (v, level) = Delete_Queue(); for (p=G[v].edge; p; p=p->next) // 所有與 v 相鄰的頂點 w) { if (visited[p->v] == 0) { visited(p->v) = 1; q = newSPNodeLevel(p, level); addAsFirst(T[u].link, q); Add_Queue(p->v, q->level); if (q->level>T[u].radius) T[u].radius = q->level; } } } return T[u].radius; } int findSPMinRadius() { int i, r, minSPRadius = MAXINT; minSPRoot; for (i=0; i<n; i++) { if ((r=radiusBFSSP(i)) < minSPRadius) minSPRoot = i; } return minSPRoot; // minSPRadius 即為 T[minSPRoot].radius } ``` --- --- ## 第七章 排序 --- ### 1. 請比較下列排序演算法的異同: <br> &emsp; (a) 挑選排序、氣泡排序和插入排序。 <br> &emsp; (b) Shell 排序和合併排序。 (a) | |額外儲存空間|時間複雜度|穩定性| |:------:|:------:|:------:|:------:| | 挑選排序 | *O*(1) | *O*($n^2$) | X | | 氣泡排序 | *O*(1) | *O*($n^2$) | O | | 插入排序 | *O*(1) | *O*($n^2$) | O | (b) | |額外儲存空間|時間複雜度|穩定性| |:------:|:------:|:------:|:------:| | Shell排序 | X | *O*(n logn) | X | | 合併排序 | X | *O*(n logn) | O | --- ### 3. 請說明本章所有排序演算法在輸入數列以下面兩種排序時的表現: <br> &emsp; (a) 排序完成 <br> &emsp; (b) 反向排序完成 | 排序法 |排序完成|反向排序完成| |:------:|:------:|:------:| | 挑選排序 | *O*($n^2$) | *O*($n^2$) | | 插入排序 | *O*($n$) | *O*($n^2$) | | 氣泡排序 | *O*($n$) | *O*($n^2$) | |Shell排序| 與資料是否已排序無關 | | 合併排序 | 與資料是否已排序無關,皆為 *O*($nlogn$) | | 快速排序 | *O*($n^2$) | *O*($n^2$) | | 基數排序 | 與資料是否已排序無關,皆為 *O*($nlogn$) | | 堆積排序 | 與資料是否已排序無關,皆為 *O*($nlogn$) | ![](https://i.imgur.com/fOjRfgT.png) --- ### 5. 請實作不同版本的快速排序法: <br> &emsp; (a) 遞迴版 <br> &emsp; (b) 利用堆疊和以迴圈控制版 <br> &emsp; (c\) 當 left~right 間的資料量少於 10 時,直接用挑選或排序;不必再遞迴呼叫(或 push 入堆疊) <br> &emsp; (d) 取一個 0~n−1 的亂數 i,令 target 為 data[i] <br> &emsp; (e) 令 target 為 data[0]、data[n/2]和 data[n-1]的中位數。 <br> &emsp; 並利用題 2 的實驗設計製作圖表,觀察並討論實驗結果。 (a) ```cpp= # include <iostream> using namespace std; void swap(int *x, int *y) { int t; t = *x; *x = *y; *y = t; } void Quick(int data[], int left, int right) { int i, j, target; if(left < right) { i = left + 1; j = right; target = data[left]; do { while((data[i] < target) && (i <= j)) i++; while((data[j] >= target) && (i <= j)) j--; if(i < j) swap(&data[i], &data[j]); }while(i < j); if(left < j) swap(&data[left], &data[j]); Quick(data, left, j-1); Quick(data, j+1, right); } } int main () { int n, i, count = 1; int data[100], temp[100]; cin>>n; for (i = 0; i < n; i++) cin>>data[i]; Quick(data, 0, n-1); for (i = 0; i < n; i++) cout<<data[i]<<" "; cout<<endl; } ``` ![](https://i.imgur.com/OtupQI5.png) (b) ```cpp= # include <iostream> using namespace std; void swap(int *x, int *y) { int t; t = *x; *x = *y; *y = t; } int top=-1; struct StackNode { int l, r; }*stack; void push(int left, int right) { stack[++top].l = left; stack[top].r = right; } struct StackNode *pop() { struct StackNode *p = new StackNode; *p = stack[top--]; return p; } void QuickSort(int data[], int n) { stack = new StackNode[n]; int left = 0; int right = n - 1; int i, j, target; push(left, right); while(top != -1) { struct StackNode *p = pop(); left = p->l; right = p->r; target = data[left]; i = left +1 ; j = right; do { while((data[i] < target) && (i <= j)) i++; while((data[j] >= target) && (i <= j)) j--; if(i < j) swap(&data[i], &data[j]); }while(i < j); if(left < j) swap(&data[left], &data[j]); if((j+1) < right) push(j+1, right); if(left < (j-1)) push(left, j-1); } } int main () { int n, i, count = 1; int data[100], temp[100]; cin>>n; for (i = 0; i < n; i++) { cin>>data[i]; } QuickSort(data, n); for (i = 0; i < n; i++) { cout<<data[i]<<" "; } cout<<endl; } ``` ![](https://i.imgur.com/w76azor.png) (c) 請自行練習。 (d) ```cpp= # include <cstdlib> # include <time.h> # include <iostream> using namespace std; int partition(int arr[], int left, int right) { int pivot = arr[right]; int i = (left - 1); for (int j = left; j <= right - 1; j++) { if (arr[j] <= pivot) { i++; swap(arr[i], arr[j]); } } swap(arr[i + 1], arr[right]); return (i + 1); } int partition_r(int arr[], int left, int right) { srand(time(NULL)); int random = left + rand() % (right - left); swap(arr[random], arr[right]); return partition(arr, left, right); } void quickSort(int arr[], int left, int right) { if (left < right) { int pi = partition_r(arr, left, right); quickSort(arr, left, pi - 1); quickSort(arr, pi + 1, right); } } void printArray(int arr[], int size) { int i; for (i = 0; i < size; i++) cout<<arr[i]<<" "; } int main() { int n; cin>>n; int arr[100]; for(int i = 0; i<n;i++) cin>>arr[i]; quickSort(arr, 0, n - 1); cout<<"Sorted array:"; printArray(arr, n); return 0; } ``` ![](https://i.imgur.com/niCcKTX.png) --- ### 7. 請實作不同版本的堆積排序法: <br> &emsp; (a) 用陣列實作最小(大)堆積 <br> &emsp; (b) 用鍵結串列實作最小(大)堆積。 <br> &emsp; 並利用題 2 的實驗設計製作圖表,觀察並討論實驗結果。 請依演算法 7-12 實作本習題,並比較行結果。 --- ### 9. 證明當較小的子串列先排序時,那麼快速排序中的遞迴,可以用深度為*O*($logn$) 的堆疊來模擬。 快速排序演算法,最好的情況就是每次剛好分割一半,時間複雜度就可達到 *O*($nlogn$),需要 *O*($logn$) 次的分割;而一次分割後先執行某一半資料的排序,另一半(的起點和終點)放堆疊,需要*O*(1) 的堆疊空間,總共分割 *O*($logn$) 次,會需要有 *O*($logn$) 的堆疊空間。若分割不平均,且較小的子數列先排序,較大的子數列要先放堆疊,空間 *O*(1)。小的子數列排序時所用的堆疊空間肯定少於 *O*($logn$),而它排序完成後,曾用到的堆疊空間(少於 *O*($logn$))會釋放出來,堆疊剩當時分割後較大的子數列(起點、終點)在堆疊。pop 後堆疊已空;數列又分割,又是小數列先排序—所用的堆疊空間肯定少於 *O*($logn$)…。以此類推,深度為 *O*($logn$) 的堆疊空間肯定足夠。 --- ### 11. 請舉例說明基數排序的最差情形。 當要比較的數字中,大部分的數字位數為 s,但只有極少部分的數字位數為 k,當 s 極小而 k 極大時會有最差情形;也就是說因為有極大的 k 值,使得所有 位數皆要做一次排序,排了 k 次,但大部分只的值其實只須排 s 次就完成。例如: 3,1,5,20,20360051470009000358454718 --- ### 13. 設計一個程序,將數列 $(x_1, x_2, ... , x_n)$ 向右環形移動 $p$ 個位置,其中 $0 ≤ p ≤ n$。此程序應有 *O*($n$) 的時間複雜度,而其空間複雜度應為 *O*(1)。 ```cpp= void Reverse(int *list, int first, int last) { int firstindex = first, lastindex = last; while(firstindex < lastindex) { //swap int temp = list[firstindex]; list[firstindex] = list[lastindex]; list[lastindex] = temp; firstindex++; lastindex--; } } void RightCircularShift(int *list, int p, int n) { if (p>0 && p<n) { Reverse(list, 0, n-1); Reverse(list, 0, p-1); Reverse(list, p, n-1); } } ``` *O*($n$)+*O*($p$)+*O*($n-p$) = *O*($n$) ---

    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 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