---
# System prepended metadata

title: 資料結構與演算法 習題解答 奇數題

---

# 資料結構與演算法 習題解答 奇數題
## 第一章 基本概念
---
### 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$)

---