113 年 國立臺北大學資訊工程研究所《資料結構與演算法》

📄 試題原卷 免費註冊後即可對照原始考卷 PDF免費註冊

第 1 題15 分

  1. (15分) Run the Prim's algorithm to generate a minimum spanning tree for the following graph. It is required that the initial vertex is s. Run the algorithm step by step to generate your tree. What is the tree weight?

🖼️【此處有附圖,請對照原卷】

🖼️ 本題含圖表,以下為原卷對應頁面:
原卷第 1 頁

登入後即可作答並保存紀錄。

這一題的完整詳解

核心觀念

本題考查最小生成樹(Minimum Spanning Tree, MST)與 Prim 演算法。對連通且帶權重的無向圖,最小生成樹必須:

  • 包含所有頂點;
  • 不形成迴圈;
  • 具有 ∣V∣−1|V|-1 條邊;
  • 總權重最小。

Prim 演算法從指定起點 ss 開始,每次選擇「一端已在樹中、另一端尚未加入,且權重最小」的邊,逐步擴張生成樹。

解題方法

本題要求初始頂點為 ss,正確作法如下:

  1. 初始時令
    T={s}.T=\{s\}.

  2. 從所有連接 ss 與其他頂點的邊中,選擇權重最小者 (s,v1)(s,v_1),加入生成樹。

  3. 此時樹中已有 {s,v1}\{s,v_1\},從所有「一端在樹中、另一端不在樹中」的邊中,選擇權重最小者並加入。

  4. 重複上述步驟,直到所有頂點都加入。若圖有 nn 個頂點,最後得到 n−1n-1 條邊。

每一步可整理為:

🔒

後續完整解題步驟與【答案】

免費註冊,享三天全站完整詳解閱覽。

免費註冊

第 2 題15 分

  1. (15分) Given a file, we found that it consists of seven symbols a,b,c,d,e,f,g and their frequencies are as follows.
    Alphabet | a | b | c | d | e | f | g
    ------- | -------- | -------- | -------- | -------- | -------- | -------- | --------
    Frequency in the file | 12 | 26 | 43 | 13 | 9 | 5 | 15

Use Huffman coding to encode these 7 symbols to achieve the minimum file size.

登入後即可作答並保存紀錄。

這一題的完整詳解

核心觀念

本題考查 Huffman coding。Huffman 編碼是一種不定長度、前綴碼(prefix code):

  • 出現頻率越高的符號,通常配置越短的編碼。
  • 出現頻率越低的符號,通常配置越長的編碼。
  • 任一符號的編碼不能是另一符號編碼的前綴,以確保能唯一解碼。
  • Huffman 演算法每次選取頻率最小的兩個節點合併,重複此步驟即可得到最小加權路徑長度。

檔案所需的最小 bit 數為:

總位元數=∑ifiℓi\text{總位元數} = \sum_{i} f_i \ell_i

其中 fif_i 是符號頻率,ℓi\ell_i 是該符號的編碼長度。


解題方法

將所有符號依頻率由小到大排列:

符號feadgbc
頻率591213152643

每次取出頻率最小的兩個節點合併:

  1. 合併 ff 與 ee:
5+9=145+9=14
  1. 合併 aa 與 dd:
12+13=2512+13=25
  1. 合併頻率 1414 與 gg:
14+15=2914+15=29
  1. 合併頻率 2525 與 bb:
25+26=5125+26=51
  1. 合併頻率 2929 與 cc:
29+43=7229+43=72
  1. 最後合併 5151 與 7272:
51+72=12351+72=123

Huffman 樹可表示如下:

                 123
                /   \
              51     72
             /  \   /  \
            b   25 c   29
               / \     / \
              a   d   g   14
                         / \
                        f   e

令左分支為 00、右分支為 11,可得到一組合法的 Huffman 編碼:

🔒

後續完整解題步驟與【答案】

免費註冊,享三天全站完整詳解閱覽。

免費註冊

第 3 題5 分

  1. (5分) Give an asymptotic upper bound for the function T(n) where T(n) satisfies the recurrent equation T(n)=5T(n/4)+ n.

登入後即可作答並保存紀錄。

這一題的完整詳解

核心觀念

本題考查遞迴式的漸近分析,可使用 Master Theorem:

T(n)=aT(nb)+f(n)T(n)=aT\left(\frac{n}{b}\right)+f(n)

其中:

  • a=5a=5:每次分成 55 個子問題
  • b=4b=4:每個子問題的規模為 n/4n/4
  • f(n)=nf(n)=n:每層額外花費的非遞迴成本

Master Theorem 需比較:

f(n)=nf(n)=n

與

nlog⁡ba=nlog⁡45n^{\log_b a}=n^{\log_4 5}

因為 5>45>4,所以:

log⁡45>1\log_4 5>1

因此:

nlog⁡45n^{\log_4 5}

的成長速度高於 nn。


解題方法:使用 Master Theorem

Master Theorem 的第一種情況為:

f(n)=O(nlog⁡ba−ε)f(n)=O\left(n^{\log_b a-\varepsilon}\right)

其中 ε>0\varepsilon>0,則:

T(n)=Θ(nlog⁡ba)T(n)=\Theta\left(n^{\log_b a}\right)

本題中:

a=5,b=4,f(n)=na=5,\qquad b=4,\qquad f(n)=n

且:

n=n1n=n^1

由於:

log⁡45>1\log_4 5>1

可取某個 ε>0\varepsilon>0,使得:

1=log⁡45−ε1=\log_4 5-\varepsilon

因此符合 Master Theorem 第一種情況,得到:

T(n)=Θ(nlog⁡45)T(n)=\Theta\left(n^{\log_4 5}\right)

題目要求的是漸近上界,因此可寫成:

T(n)=O(nlog⁡45)\boxed{T(n)=O\left(n^{\log_4 5}\right)}

數值上:

log⁡45=log⁡5log⁡4≈1.161\log_4 5=\frac{\log 5}{\log 4}\approx 1.161

所以也可表示為:

T(n)=O(n1.161)T(n)=O(n^{1.161})

遞迴樹直觀驗算

🔒

後續完整解題步驟與【答案】

免費註冊,享三天全站完整詳解閱覽。

免費註冊

第 4 題15 分

  1. (15分) Let k be O(n). Describe an O(n)-time algorithm that, given a set S = {n₁, n₂, ..., nₖ} of k integers and another integer x, determines whether or not there exist two elements in S whose product is exactly x.

登入後即可作答並保存紀錄。

這一題的完整詳解

核心觀念

題目要求判斷是否存在兩個不同元素 a,b∈Sa,b\in S,使得

ab=x.ab=x.

關鍵是將乘法條件改寫為查找問題:

b=xa.b=\frac{x}{a}.

因此,依序取出每個元素 aa,只要檢查:

  1. aa 是否為 00;
  2. x/ax/a 是否為整數;
  3. x/ax/a 是否存在於 SS;
  4. x/a≠ax/a\ne a,確保使用的是兩個不同元素。

使用雜湊表儲存集合元素,即可在期望 O(1)O(1) 時間完成存在性查詢。


解題方法

先將所有元素存入雜湊表 HH。

特別處理 x=0x=0:

  • 乘積為 00 必須至少有一個元素為 00;
  • 因為題目要求兩個不同元素,所以還必須有另一個元素;
  • 對集合 SS 而言,只要 0∈S0\in S 且 k≥2k\ge 2,就一定存在符合條件的兩個元素。

當 x≠0x\ne 0 時,集合中符合條件的兩個元素都不會是 00。逐一檢查每個 a∈Sa\in S,計算

b=xa.b=\frac{x}{a}.

若 bb 為整數、b∈Sb\in S 且 b≠ab\ne a,則找到兩個元素 a,ba,b,回傳成功;全部檢查完仍未找到,則回傳失敗。

關鍵偽程式碼

TwoProduct(S, x):
    H ← 建立空的雜湊表

    for each a in S:
        H.insert(a)

    if x = 0:
        if 0 ∈ H and |S| ≥ 2:
            return TRUE
        else:
            return FALSE

    for each a in S:
        if a = 0:
            continue

        if x mod a = 0:
            b ← x / a

            if b ≠ a and H.contains(b):
                return TRUE

    return FALSE

例如 S={2,3,5,10}S=\{2,3,5,10\}、x=30x=30:

🔒

後續完整解題步驟與【答案】

免費註冊,享三天全站完整詳解閱覽。

免費註冊

第 5 題15 分

  1. (5分)(A)使用quick sort 排序數列 608045 2 36 98 11 47 528975637。並且說明每次移動數字時, 所使用的條件和基準。
    (5分)(B)將此 inorder statement : A*(B+C)+E*F-H/I 轉為 postorder,並畫出它的二元樹。
    (10分)(C)設計一個算法使用 stack 來實現上述 inorder 轉 postorder,需解釋如何處理括號和運算式的優先權。

登入後即可作答並保存紀錄。

這一題的完整詳解

核心觀念

本題涵蓋三個重點:

  1. Quick sort

    • 採用分割(partition)將資料分成「小於基準」與「大於基準」兩側。
    • 每次選定一個 pivot,完成後 pivot 位於排序後的正確位置。
    • 平均時間複雜度為 O(nlog⁡n)O(n\log n),最壞情況為 O(n2)O(n^2)。
  2. Inorder 與 Postorder

    • Inorder:左子樹 →\rightarrow 根 →\rightarrow 右子樹。
    • Postorder:左子樹 →\rightarrow 右子樹 →\rightarrow 根。
    • 運算子優先權為:
      ∗/  >  +− ⁣*/ \;>\; +-\!
    • 同一優先權的運算子採左結合。
  3. Stack 轉換運算式

    • 運算元直接輸出。
    • 運算子暫存於 stack。
    • 遇到括號時,左括號作為區隔,右括號則將運算子彈出至左括號為止。

題目中的數列疑似因排版遺失空格,以下依合理格式解讀為
60, 80, 45, 2, 36, 98, 11, 47, 52, 89, 75, 63, 760,\ 80,\ 45,\ 2,\ 36,\ 98,\ 11,\ 47,\ 52,\ 89,\ 75,\ 63,\ 7。


(A) 使用 Quick Sort 排序

分割規則

採用以下升冪排序規則:

  • 每個子數列的第一個元素作為 pivot。
  • 左指標 ii 從左向右尋找第一個大於 pivot 的數字。
  • 右指標 jj 從右向左尋找第一個小於 pivot 的數字。
  • 當 i<ji<j 時,交換 A[i]A[i] 與 A[j]A[j]。
  • 當 i≥ji\geq j 時,交換 pivot A[L]A[L] 與 A[j]A[j],pivot 即完成定位。

第一次分割:pivot = 60

原數列:

60, 80, 45, 2, 36, 98, 11, 47, 52, 89, 75, 63, 760,\ 80,\ 45,\ 2,\ 36,\ 98,\ 11,\ 47,\ 52,\ 89,\ 75,\ 63,\ 7
  1. ii 遇到 80>6080>60 停止;jj 從右側找到 7<607<60:
60, 80‾,45,2,36,98,11,47,52,89,75,63,7‾60,\ \underline{80},45,2,36,98,11,47,52,89,75,63,\underline{7}

交換 8080 與 77:

60, 7,45,2,36,98,11,47,52,89,75,63,8060,\ 7,45,2,36,98,11,47,52,89,75,63,80
  1. ii 找到 98>6098>60;jj 從右側找到 52<6052<60:

交換後:

60, 7,45,2,36,52,11,47,98,89,75,63,8060,\ 7,45,2,36,52,11,47,98,89,75,63,80
  1. 此時左指標與右指標交會,jj 停在 4747。將 pivot 6060 與 4747 交換:
47, 7, 45, 2, 36, 52, 11, 60, 98, 89, 75, 63, 8047,\ 7,\ 45,\ 2,\ 36,\ 52,\ 11,\ 60,\ 98,\ 89,\ 75,\ 63,\ 80

因此:

[47,7,45,2,36,52,11]60[98,89,75,63,80][47,7,45,2,36,52,11]\quad 60\quad [98,89,75,63,80]

左半部繼續分割

以 4747 為 pivot:

[47,7,45,2,36,52,11]→[11,7,45,2,36]47[52][47,7,45,2,36,52,11] \rightarrow [11,7,45,2,36]\quad 47\quad [52]

以 1111 為 pivot:

[11,7,45,2,36]→[2,7]11[45,36][11,7,45,2,36] \rightarrow [2,7]\quad 11\quad [45,36]

繼續處理:

[2,7]→[2]7[2,7]\rightarrow [2]\quad 7 [45,36]→[36]45[45,36]\rightarrow [36]\quad 45

因此左半部排序為:

2, 7, 11, 36, 45, 47, 522,\ 7,\ 11,\ 36,\ 45,\ 47,\ 52

右半部繼續分割

以 9898 為 pivot:

[98,89,75,63,80]→[80,89,75,63]98[98,89,75,63,80] \rightarrow [80,89,75,63]\quad 98

以 8080 為 pivot:

[80,89,75,63]→[75,63]80[89][80,89,75,63] \rightarrow [75,63]\quad 80\quad [89]

繼續處理:

[75,63]→[63]75[75,63]\rightarrow [63]\quad 75

因此右半部排序為:

63, 75, 80, 89, 9863,\ 75,\ 80,\ 89,\ 98

排序結果

合併左右兩側:

2, 7, 11, 36, 45, 47, 52, 60, 63, 75, 80, 89, 98\boxed{2,\ 7,\ 11,\ 36,\ 45,\ 47,\ 52,\ 60,\ 63,\ 75,\ 80,\ 89,\ 98}

(B) Inorder 轉為 Postorder 並畫出二元樹

原式:

A∗(B+C)+E∗F−H/IA*(B+C)+E*F-H/I

運算優先順序

先處理括號,再處理乘除,最後處理加減。因此完整結構為:

(A∗(B+C)+E∗F)−(H/I)\bigl(A*(B+C)+E*F\bigr)-\bigl(H/I\bigr)

其二元樹如下:

                 -
               /   \
              +     /
             / \   / \
            *   * H   I
           / \ / \
          A  + E  F
            / \
           B   C

轉換過程

各子運算式的 Postorder:

  • B+CB+C:

    B C +B\ C\ +

  • A∗(B+C)A*(B+C):

    A B C + ∗A\ B\ C\ +\ *

  • E∗FE*F:

🔒

後續完整解題步驟與【答案】

免費註冊,享三天全站完整詳解閱覽。

免費註冊

第 6 題10 分

  1. (10分)使用linked list運用C語言實作stack:
    typedef struct node { int value; node *next;}
    void push (node **top, int new_value) { // assume top is not null
    node *new_node = (node *) malloc (sizeof (node));
    // 實作以下敘述
    }
    // add new_node to stack by top
    int pop (node **top) { // assume top is not null
    int value;
    node *deleting_node = *top;
    // 實作以下敘述
    }
    // remove the deleting node from stack, get the value of deleting node
    free(deleting_node);
    return value;

登入後即可作答並保存紀錄。

這一題的完整詳解

核心觀念

本題考查以單向 linked list 實作 stack,以及指標與動態記憶體的操作。

Stack 遵守 LIFO(Last In, First Out,後進先出):

  • push:將新元素放到 stack 頂端。
  • pop:取出並刪除 stack 頂端元素。
  • top:指向目前 stack 的第一個節點。
  • 空 stack 時,top == NULL。

由於 linked list 的頭端就是 stack 頂端,因此兩種操作都只需修改頭端指標,時間複雜度皆為 O(1)O(1)。

題目中的結構宣告依標準 C 語法應寫成:

typedef struct node {
    int value;
    struct node *next;
} node;

解題方法

push 的操作順序

新節點加入 stack 頂端時,必須依序完成:

  1. 將資料存入新節點的 value。
  2. 讓新節點指向原本的 stack 頂端。
  3. 將 top 更新為新節點。

假設原本的 stack 如下:

top
 ↓
[A] → [B] → NULL

加入新節點 C 後:

top
 ↓
[C] → [A] → [B] → NULL

因此程式為:

void push(node **top, int new_value) {
    node *new_node = (node *) malloc(sizeof(node));

    new_node->value = new_value;
    new_node->next = *top;
    *top = new_node;
}

top 的型別是 node **,因為 push 必須修改呼叫端的 stack 頂端指標。若只傳入 node *top,修改的只是函式內部副本,無法更新真正的 stack 頂端。

pop 的操作順序

刪除 stack 頂端時,必須依序完成:

  1. 以 deleting_node 暫存目前頂端節點。
  2. 先讀出該節點的資料。
  3. 將 top 往下一個節點移動。
  4. 釋放原本的頂端節點。
  5. 回傳取出的資料。

假設原本為:

top
 ↓
[A] → [B] → NULL

執行 pop 後:

top
 ↓
[B] → NULL

程式為:

int pop(node **top) {
    int value;
    node *deleting_node = *top;

    value = deleting_node->value;
    *top = deleting_node->next;

    free(deleting_node);
    return value;
}

完整實作如下:

🔒

後續完整解題步驟與【答案】

免費註冊,享三天全站完整詳解閱覽。

免費註冊

第 7 題20 分

  1. (20分)使用stack運用C語言實作老鼠走迷宮問題(Maze path search) :
    void path()
    {
    // assume int maze[30][30] and offsets move[8] has been set in main function
    // typedef struct {int v; int h;} offsets;
    int i, row, col, next_row, next_col, dir, found = 0, top=0, mark[30][30];
    typedef struct { int row; int col; int dir;} element
    element position, stack[100];
    mark[1][1] = 1; stack[0].row = 1; stack[0].col = 1; stack[0].dir = 0;
    //實作以下敘述
    // Use stack to get current row, col, dir
    // Use dir to calculate next row, next_col
    // Use next_row, next_col to check this move (1) arrive exit, or
    // (2) become a legal move; add this move into the stack, or
    // (3) check other dir
    }

登入後即可作答並保存紀錄。

這一題的完整詳解

核心觀念

本題考查:

  • 以 stack 實作深度優先搜尋(Depth-First Search, DFS)。
  • 使用 回溯法(backtracking) 解迷宮。
  • 以 mark[row][col] 記錄已走過的位置,避免重複搜尋。
  • 每個 stack 元素記錄:
(row,col,dir)(\text{row},\text{col},\text{dir})

其中 dir 表示目前位置下一個尚未嘗試的方向。

move[8] 儲存八個移動方向:

typedef struct {
    int v;    // row 的變化量
    int h;    // col 的變化量
} offsets;

由目前位置與方向計算下一格:

next_row=row+move[dir].v\text{next\_row}=\text{row}+\text{move[dir].v} next_col=col+move[dir].h\text{next\_col}=\text{col}+\text{move[dir].h}

解題方法

假設:

  • 起點為 (1,1)。
  • 出口為 (exit_row, exit_col)。
  • maze[r][c] == 0 表示可通行。
  • maze[r][c] == 1 表示牆壁。
  • 迷宮外圍已設為牆壁,因此不必另外處理陣列越界。
  • mark[r][c] == 1 表示該位置已走過。

當目前位置有合法方向時:

  1. 將目前位置尚未嘗試的方向取出。
  2. 計算下一個位置。
  3. 若下一位置是出口,表示找到路徑。
  4. 若下一位置是合法且未走過的位置:
    • 將目前位置更新為「下一次從哪個方向繼續嘗試」。
    • 將下一位置 push 入 stack。
  5. 若八個方向都無法前進,表示此位置走不通,將它 pop,回到上一格繼續嘗試。

關鍵程式碼

🔒

後續完整解題步驟與【答案】

免費註冊,享三天全站完整詳解閱覽。

免費註冊

其他考古題