108 年 國立臺灣大學資訊網路與媒體研究所《資料結構與演算法》
第 1 題10 分
- (10%) Let be the number of different permutations obtainable by passing the numbers through a stack and deleting in all possible ways.
(a) Give the recursive formula for .
(b) Give the analytic formula for .
登入後即可作答並保存紀錄。
核心觀念
本題考的是:
- 堆疊(stack)的後進先出(LIFO)特性;
- 合法的 push/pop 操作序列;
- Catalan number(卡特蘭數)的遞迴式與封閉形式。
輸入順序固定為 。每個數字依序進入堆疊,但可以在任意時刻刪除(pop)堆疊頂端元素,因此產生不同的輸出排列。
例如 時,合法排列包括:
但 無法產生,因為要先輸出 ,必須先將 都放入堆疊;此時 輸出後, 必須先於 輸出,不可能接著輸出 。
解題方法
將堆疊操作轉換成括號序列
將「放入堆疊」視為左括號,將「刪除堆疊頂端元素」視為右括號。
由於任何時刻刪除的元素都必須是目前堆疊頂端,因此刪除次數不能超過放入次數。最後必須完成 次放入與 次刪除。
所以,合法操作序列正好對應於長度為 的合法括號序列,也就是 Dyck paths。其數量即為第 個 Catalan number。
(a) 遞迴公式
令 表示 個元素可形成的合法堆疊排列數,並定義
考慮第一個元素與其配對的刪除操作。
在這次刪除之前,堆疊內部可能有 個元素;刪除該元素之後,剩下的操作則包含 個元素。這兩部分可以獨立形成合法操作,因此共有
種方式。
對所有可能的 加總,得到:
因此遞迴式為:
前幾項為:
(b) 解析公式
這正是 Catalan number 的標準形式,因此:
第 2 題5 分
- (5%) Convert the expression "" into a postfix form.
登入後即可作答並保存紀錄。
核心觀念
本題考查中序表示式(infix expression)轉後序表示式(postfix expression),也就是將運算子移到其兩個運算元之後。
各運算子的優先順序為:
同一優先順序的運算子採由左至右結合,因此:
後序表示式不需要括號,因為運算順序已由運算子的位置明確表示。
解題方法
原式為:
先依括號與運算子優先順序分解。
第一步:處理括號
轉為後序形式:
第二步:處理
將 與前一步結果相乘:
後序形式為:
第三步:處理
將 除以前一步結果:
後序形式為:
第四步:處理括號
後序形式為:
第 3 題10 分
- (10%) Draw the final min-heap tree after the following operations: insert 7, insert 4, insert 3, insert 1, delete min, insert 9, insert 2, insert 5, delete min, delete min.
登入後即可作答並保存紀錄。
核心觀念
本題考查最小堆積(min-heap)的兩項性質:
- 完全二元樹:除最後一層外,每層皆填滿,最後一層由左至右排列。
- 最小堆積性質:每個父節點的值皆小於或等於其子節點,即
使用陣列表示時:
- 父節點:
- 左子節點:
- 右子節點:
插入時,元素先放在陣列末端,再向上調整(up-heap);刪除最小值時,移除根節點,將最後元素移至根部,再向下調整(down-heap)。
解題方法
以下以陣列表示每一步的 min-heap。
1. insert 7
樹狀結構:
7
2. insert 4
4 先放在末端,再與父節點 7 交換:
4
/
7
3. insert 3
3 放入下一個位置,與父節點 4 比較並交換:
3
/ \
7 4
4. insert 1
1 放在陣列末端:
1 小於父節點 7,因此交換:
此時 1 已大於根節點 3,不需繼續上移。
3
/ \
1 4
/
7
5. delete\ min
移除根節點 3,將最後元素 7 移至根部:
7 與較小的子節點 1 交換:
1
/ \
7 4
6. insert 9
9 放入陣列末端,且 9 大於父節點 7:
1
/ \
7 4
/
9
7. insert 2
2 放入陣列末端:
2 小於父節點 7,因此交換:
第 4 題8 分
- (8%) Describe how you can construct a heap of n keys with O(n) complexity. You can simply use n = 15 as an example to explain your idea. (You don't need to prove the complexity.)
登入後即可作答並保存紀錄。
核心觀念
本題考查「以自底向上的方式建立 Heap」,也就是 Bottom-Up Build-Heap。
以最大堆(max heap)為例,必須滿足:
陣列表示的完全二元樹中:
- 第 個節點的父節點為
- 左子節點為
- 右子節點為
- 的節點全部是葉節點
葉節點本身已經符合 Heap 條件,因此只需從最後一個內部節點開始,反向處理至根節點。
解題方法
建立最大堆的演算法如下:
- 將 個鍵值先依序放入完全二元樹。
- 從第 個節點開始。
- 對每個內部節點執行
sift-down:- 比較該節點與兩個子節點。
- 將較大的子節點與父節點交換。
- 若交換後仍違反最大堆條件,繼續向下調整。
- 處理順序為:
此方法建立 Heap 的時間複雜度為:
不可使用逐一插入的方式,因為每次插入可能需要向上調整 ,總複雜度會是 。
以 為例
假設原始陣列為:
其完全二元樹如下:
1
/ \
2 3
/ \ / \
4 5 6 7
/ \ / \ / \ / \
8 9 10 11 12 13 14 15
因為:
所以從節點 開始,依序處理節點 。
處理節點 7
節點 的子節點為 ,最大值為 ,交換:
節點 7:7 → 15
節點 15:15 → 7
處理節點 6
節點 的子節點為 ,交換較大的 :
節點 6:6 → 13
節點 13:13 → 6
處理節點 5
節點 的子節點為 ,交換較大的 :
節點 5:5 → 11
節點 11:11 → 5
處理節點 4
節點 的子節點為 ,交換較大的 :
第 5 題20 分
- (20%) Quicksort is a fast and commonly used comparison-based sorting algorithm. Below you can find a version of quicksort algorithm in
pseudo code format.
QUICKSORT (A, p,r)
1 if p <r
2 q = PARTITION(A, p, r)
3 QUICKSORT (A, p, q - 1)
4 QUICKSORT(A, q + 1, r)
PARTITION(A, p,r)
1 x = A[r]
2 i = p - 1
3 for j = p to r - 1
4 if A[j] < x
5 i = i + 1
6 exchange A[i] with A[j]
7 exchange A[i + 1] with A[r]
8 return i + 1
(a) (6%) True or false:
(1) The output of QUICKSORT() above is a non-increasing sorted sequence. (2%)
(2) The QUICKSORT() above is a stable sorting algorithm. (2%)
(3) The QUICKSORT() above is an in-place sorting algorithm. (2%)
(b) (4%) The given version of QUICKSORT above cannot efficiently process the cases where in the input array there are a large number of
repeated elements. Analyze the worst-case running time which occurs when all n keys in the input array A are identical. Please give
it in big-O notation in terms of n.
(c) (10%) To address the previously mentioned problematic case, we will now develop a new partition algorithm PARITITIONTHREE() that
separates the input keys into three groups, the ones that are smaller than, that are identical to, and that are larger than the pivot
key x. The return values q1 and q2 represent the indices of the first and the last keys of the group that equals the pivot key,
respectively. Fill the blanks (A), (B), (C) in PARITIONTHREE() below to complete the implementation, such that QUICKSORTTHREE() runs in
O(n)-time when the input array has all n elements identical.
QUICKSORTTHREE(A, p, r)
1 if p < r
2 (q1, q2) = PARTITIONTHREE(A, p, r)
3 QUICKSORTTHREE(A, p, q1 - 1)
4 QUICKSORTTHREE(A, q2 + 1, r)
PARTITIONTHREE(A, p, r)
1 x = A[r]
2 q1 = p
3 q2 = r
4 j = p
5 while j ≤ q2
6 if A[j] < x
7 exchange A[j] with A[q1]
8 // (A)
9 j = j + 1
10 elseif A[j] > x
11 exchange A[j] with A[q2]
12 // (B)
13 else // A[j] == x
14 // (C)
15 return (q1, q2)
登入後即可作答並保存紀錄。
參考書等級:中等(如《Introduction to Algorithms》CLRS 第7章)
(a) 判斷
-
輸出為非遞增序列 False
透過PARTITION將比基準x小的元素放在左側,較大的放右側,最終得到的是 遞增(由小到大)序列,非遞增(由大到小)不成立。 -
QUICKSORT 為穩定排序 False
在if A[j] < x與if A[j] > x的交換過程中,若兩鍵相等仍會被交換,會改變相同鍵的相對次序,故不具穩定性。 -
QUICKSORT 為原地(in‑place)排序 True
只使用常數個額外變數i, j, p, r, q, x,無額外隨機存取陣列,故為原地演算法。
(b) 全部鍵相同的最壞執行時間
當所有 個鍵相同,PARTITION 每次都只把基準 移至最左或最右,使遞迴規模減少 1。遞迴關係:
解得 ,故最壞情形為 。
(c) 三路切分實作
第 6 題13 分
- (13%) When implementing a hash table, one of the commonly used collision resolution methods is open addressing. Let k be the key, and m be the size of the hash table. Assume each table entry can hold one key.
(a) (3%) The simplest open addressing method is called linear probing, where if a slot is already occupied, the following slots are "probed" sequentially. Assume an auxiliary hash function h'(k) = k mod m. Please give the hash function h(k, i) of linear probing with the given auxiliary function, where i is the number of times the hash table has been probed with this key (and thus starting from 0).
(b) (5%) Linear probing is easy to implement but suffers from the problem of primary clustering. A more complex open addressing method which can mitigate this problem is double hashing, using a hash function in the form of h(k, i) = (h₁(k) + i*h₂(k)) mod m, with two auxiliary hash functions h₁(k) and h₂(k). i is the number of times the hash table has been probed with key k. Assume two auxiliary hash functions h₁(k) = k mod 16, h₂(k) = 1 + (k mod 15), and m = 16. Show the final hash table content after inserting all of the following keys to an initially empty hash table in the given order: {16, 3, 35, 67, 51, 1, 15, 31, 19, 17).
(c) (5%) Prof. Alpha proposes a different double hashing method, using two auxiliary hash functions h₁(k) = k mod 16, h₂(k) = 2(k mod 8), respectively. Assume the table size m = 16. Use an example to briefly explain how Prof. Alpha's method is problematic.
登入後即可作答並保存紀錄。
核心觀念
Open addressing 將所有資料直接存放在 hash table 中;發生碰撞時,利用 probe sequence 依序尋找下一個空槽。
一般形式為:
其中:
- :鍵值
- :探測次數,從 開始
- :hash table 大小
- :第一次探測的位置
雙重雜湊的形式為:
其中 是每次探測前進的步長。
(a)Linear Probing
線性探測每次向後移動一格,因此:
題目給定:
代入可得:
由模運算性質,也可寫成:
當 時,探測原始 hash 位置;若該位置被占用,則依序探測下一格。
(b)Double Hashing
題目給定:
依照題目順序插入各鍵值。
逐一插入
| 鍵值 | 探測位置 | 最終位置 | ||
|---|---|---|---|---|
以 為例:
探測序列為:
位置 均已占用,因此放入位置 。
最終 hash table
| 索引 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|---|
| 內容 | 16 | 1 | 空 | 3 | 17 | 31 | 空 | 空 |
第 7 題6 分
- (6%) True or false:
(a) (2%) The Dijkstra's algorithm (single run) solves the single-source shortest path problem with positive, zero, or negative edge weights, if there is no negative cycle.
(b) (2%) The Bellman-Ford algorithm (single run) solves the all-pairs shortest path problem with non-negative edge weights.
(c) (2%) The Floyd-Warshall algorithm (single run) solves the all-pairs shortest path with positive, zero, or negative edge weights, even if there are negative cycles.
登入後即可作答並保存紀錄。
核心觀念
本題考查三種最短路徑演算法的適用範圍:
- Dijkstra:解單源最短路徑,但要求所有邊權重皆非負。
- Bellman-Ford:解單源最短路徑,可處理負權重;也能偵測負環。
- Floyd-Warshall:解全點對最短路徑,可處理負權重,但不能在存在負環時定義有限的最短路徑。
若圖中存在從 到 的負環,沿著負環繞行次數越多,路徑成本便能無限下降:
因此「存在負環時仍能求得全點對最短路徑」通常是不成立的。
解題方法
逐一檢查每個演算法的:
- 解決問題類型:單源或全點對。
- 邊權重限制:是否允許負邊。
- 負環影響:是否仍存在定義良好的最短路徑。
選項分析
(a) 錯誤
Dijkstra 的確是解單源最短路徑問題,但其正確性的關鍵條件是:
也就是所有邊權重必須非負。原因是 Dijkstra 採用貪心法,一旦選出目前距離最小的頂點,就認定其最短距離已經確定。若存在負邊,之後仍可能透過其他頂點找到更短的路徑,導致先前確定的結果錯誤。
例如有以下邊:
從 出發時:
- 目前
- 目前
Dijkstra 會先確定 的距離為 。但實際上存在路徑
其成本為:
因此真正的最短距離是 ,不是 。此圖沒有負環,仍足以顯示 Dijkstra 不能處理一般負邊。
所以「沒有負環」並不足以保證 Dijkstra 正確,還必須要求所有邊權重非負。
(b) 錯誤
Bellman-Ford 解決的是單源最短路徑問題,即給定一個來源頂點 ,求:
對所有頂點 的最短距離。
它不能在單次執行中直接求出任意兩點 之間的最短距離:
第 8 題8 分
- (8%) Given a directed graph G where vertex u (in G) has at least one path to any other vertex (in G), the following pseudocode is designed to output an acyclic graph. However, in Lines 8, 10, 11, and 14, each of A, B, C, and D should be 0, 1, or 2. Write down the correct values of A, B, C, and D so that the pseudocode can successfully output an acyclic graph.
1 Cycle-Removal (G,u)
2 Initialize S as a stack
3 Label all vertices in G as Type-0
4 Label u as Type-1
5 S.push(u)
6 while S is not empty
7 v = the top vertex of S // Comment: not S.pop() here
8 if v has a Type-A neighbor w in G
9 Remove (v,w) from G
10 else if v has a Type-B neighbor w in G
11 Label w as Type-C
12 S.push(w)
13 else
14 Label v as Type-D
15 S.pop()
16 end if
17 end while
18 output G
登入後即可作答並保存紀錄。
核心觀念
本題考查:
- 深度優先搜尋(DFS)的三種頂點狀態。
- DFS 堆疊中「尚未完成處理」的頂點。
- 有向圖中的回邊(back edge)。
- 移除所有回邊即可消除有向環。
三種型態可解讀為:
- Type-0:尚未拜訪。
- Type-1:已拜訪但尚未完成,仍在 DFS 堆疊中。
- Type-2:已完成處理,已從 DFS 堆疊移除。
若目前處理頂點為 :
- 邊 若指向 Type-1 頂點,表示形成回邊,可能造成有向環。
- 邊 若指向 Type-0 頂點,表示發現新頂點,必須繼續 DFS。
- 當 沒有尚未處理的 Type-0 鄰點時,便完成 ,標記為 Type-2。
解題方法
1. 判斷 Line 8 的 Type-A
if v has a Type-A neighbor w in G
Remove (v,w) from G
要使輸出圖為無環圖,必須移除造成循環的邊。
在 DFS 中,從目前頂點 指向仍在搜尋路徑上的頂點 ,即:
這種邊稱為回邊。它可能直接形成循環,因此應將其移除。
所以:
若 ,會刪除前往尚未拜訪頂點的邊,可能破壞必要的搜尋邊,卻沒有針對循環本身處理。
若 ,會刪除指向已完成頂點的邊。這類邊不代表目前 DFS 路徑上的循環,不應作為移除對象。
2. 判斷 Line 10 的 Type-B
else if v has a Type-B neighbor w in G
Label w as Type-C
S.push(w)
當 沒有 Type-1 鄰點時,應尋找尚未拜訪的鄰點,繼續進行 DFS。
尚未拜訪的頂點是 Type-0,因此:
找到 Type-0 鄰點 後,必須將它標記為「已發現但尚未完成」,也就是 Type-1,接著推入堆疊。
因此:
若 , 仍會被視為未拜訪,可能重複推入堆疊。
若 ,則會錯誤地把尚未處理的頂點標記為已完成,破壞 DFS 狀態判斷。
3. 判斷 Line 14 的 Type-D
第 9 題10 分
- (10%) At 9am, 14 ships are leaving for the other side as shown below, where the English alphabets mean the types of those ships. For safety reasons, each ship can only stop at a pier having the same alphabet, and, if the routes of two ships cross each other, one ship needs to wait 15 minutes before leaving.
DFADBDCEBEAGCFpier
river
ship EFBEGECAEBDABD
index number
1 2 3 4 5 6 7 8 9 10 11 12 13 14
(a) (4%) Write down the maximum number of ships which can leave at 9am.
(b) (6%) Following (a), write down the index numbers (between 1 and 14) of the ships which can leave at 9am.
登入後即可作答並保存紀錄。
(a) 最大同時出航船隻數等於泊位序列與船隻序列的最長共同子序列(Longest Common Subsequence, LCS)長度。
計算 LCS:
第 10 題10 分
- (10%) The goal of this problem is to transform the following sequence of shapes into another sequence of shapes so that the total cost is minimized.
□━>>>>
The rules of transformation are shown below. For example, if you do not replace anything, the total cost is 6 + 3 + 2 + 6 + 2 + 6 + 2 + 6 + 3 + 2 = 38. If you replace the last two shapes (circle and triangle) by the rule #3, the total cost is 6+3+2+6+2+6+2+6+4 = 37. Note that you can choose not to replace a shape if the total cost can be minimized. Also, a rule of transformation can be applied more than once.
shape cost before
cost transformation
9
☐ 6 (6+3)
3
8
(6+2)
▷ 2 (3+2)
8
(2+6)
rule of
transformation
cost after
transformation
ㅇㅁㅇ
#3
7
7
4
6
11
(6+3+2)
14
(6+2+6)
#6
8
10
(2+6+2)
►ㅁㅇㅁ挈
12
7
(a) (4%) Write down the minimum total cost after transforming the given sequence.
(b) (6%) Following (a), write down the applied rules of transformation from left of the sequence to right of the sequence.
登入後即可作答並保存紀錄。
核心觀念
本題的核心為一維動態規劃(1D Dynamic Programming, DP)中的字串/序列分割問題(Sequence Segmentation Problem)。
- 最佳子結構(Optimal Substructure):
長度為 的前綴序列之最小成本,取決於更短前綴序列的最佳成本加上最後一段形狀(不替換或套用某條轉換規則)的花費。 - 重疊子問題(Overlapping Subproblems):
計算後續位置的最小成本時,會重複查詢相同前綴的最優解,適合建立一維陣列表記錄。
解題方法
1. 符號與規則整理
-
形狀原始成本(Cost Before Transformation):
- 正方形
- 圓形
- 三角形
-
轉換規則(Transformation Rules):
- Rule #1:,成本 (節省 2)
- Rule #2:,成本 (節省 1)
- Rule #3:,成本 (節省 1)
- Rule #4:,成本 (節省 2)
- Rule #5:,成本 (節省 3)
- Rule #6:,成本 (節省 4)
- Rule #7:,成本 (節省 3)
-
待轉換序列(共 10 個圖形):
- 原始總成本 。
2. 定義 DP 狀態與轉移方程式
定義 為將前綴序列 轉換後的最小總成本,基礎狀態 。
轉移方程式為:
3. 逐步推導計算
-
:
-
:
- 不替換:
- 套用 Rule #1 :
-
:
- 不替換:
- 套用 Rule #3 於 :
- 套用 Rule #5 於 :
-
:
- 不替換:
- 套用 Rule #4 於 :
-
:
- 不替換:
- 套用 Rule #2 於 :
- 套用 Rule #7 於 :
-
:
- 不替換:
- 套用 Rule #4 於 :
- 套用 Rule #6 於 :
-
:
- 不替換:
- 套用 Rule #2 於 :