113 年 國立中山大學電機工程學系碩士班丙組《資料結構》
第 1 題15 分
圖1是用類似 C/C++ 語言所撰寫的函式 (function),名為 maxHeapify,功能如下:假設 max-heap 裡頭有 個點,每個點的內容皆為正整數,儲存在陣列 裡頭 至 的地方。假設以 的左小孩為 root 的 subtree 已經是 max-heap、以 的右小孩為 root 的 subtree 也已經是 max-heap,但 可能違反 max-heap order property;在這種情況下呼叫函式 maxHeapify(A, i, n),可以使得以 為 root 的 subtree 成為 max-heap。
現在,給定陣列 ,裡頭有個元素,皆為正整數,儲存在 至 的地方;陣列 裡頭的這個元素沒有任何規律。限定必須呼叫 maxHeapify 函式,並以 recursive 的方式撰寫 buildMaxHeap 函式,使得當我們呼叫 buildMaxHeap(B, 0, n) 時,便能將具有 個元素的陣列 調整成為 root 為 的 max-heap。
註:(1) 限定使用 C、C++、或 pseudo-code 撰寫 buildMaxHeap 函式;函式裡頭如果有區域變數,就必須註解說明每一個區域變數所代表的含意。(2)如果 buildMaxHeap 函式不是以 recursive 方式撰寫,或者沒有呼叫 maxHeapify 函式,此題以 0 分計算。
void maxHeapify (unsigned int A[], int i, int n) {
int largest;
/* A[largest] 將會是 A[i], A[leftChild], A[rightChild]
這三個裡頭最大的元素 */
int leftChild; // A[leftChild] 是 A[i] 的左小孩
int rightChild; // A[rightChild] 是 A[i] 的右小孩
while (true) {
largest = i;
leftChild = 2 * i + 1;
rightChild = 2 * i + 2;
if (leftChild < n and A[leftChild] > A[largest])
largest = leftChild;
if (rightChild < n and A[rightChild] > A[largest])
largest = rightChild;
if (largest == i)
break;
else {
swap(A[i], A[largest]);
// 交換 A[i] 和 A[largest] 的內容
i = largest;
}
}
}
圖 1: maxHeapify 函式
登入後即可作答並保存紀錄。
核心觀念
maxHeapify(A, i, n) 的前提是:
- 以左小孩為根的 subtree 已經是 max-heap。
- 以右小孩為根的 subtree 已經是 max-heap。
- 只有 可能違反 max-heap order property。
因此,buildMaxHeap 必須採用「後序」處理順序:
- 先將左 subtree 建成 max-heap。
- 再將右 subtree 建成 max-heap。
- 最後呼叫
maxHeapify(B, i, n),使以 為根的 subtree 成為 max-heap。
陣列採用 0-based indexing 時:
完全二元樹中,索引 的節點沒有小孩,因此是葉節點,本身已經是 max-heap。
解題方法
從節點 開始遞迴處理:
- 若 ,代表目前節點是葉節點,直接結束。
- 否則先遞迴處理左小孩。
- 再遞迴處理右小孩。
- 左右 subtree 都完成後,呼叫
maxHeapify(B, i, n)。
關鍵程式碼
void buildMaxHeap(unsigned int B[], int i, int n) {
// i >= n / 2 表示 B[i] 是葉節點,已經是 max-heap
if (i >= n / 2)
return;
int leftChild = 2 * i + 1;
// leftChild 是 B[i] 的左小孩索引
int rightChild = 2 * i + 2;
// rightChild 是 B[i] 的右小孩索引
// 先將左 subtree 建成 max-heap
buildMaxHeap(B, leftChild, n);
// 再將右 subtree 建成 max-heap
buildMaxHeap(B, rightChild, n);
// 左右 subtree 已經是 max-heap,
// 將以 B[i] 為根的 subtree 調整成 max-heap
maxHeapify(B, i, n);
}
呼叫方式為:
buildMaxHeap(B, 0, n);
第 2 題45 分
① 假設演算法的執行時間為 ,其中 為演算法的 input size。求出 。注意: 必須是最簡 (simplest) 型式。
② 假設演算法 和演算法 都能解決問題 ,其中演算法 的執行時間為 ,演算法 的執行時間為 ,其中 為 input size。假設 的值很大,並且我們用時間複雜度來判斷演算法的好壞,那麼我們該選用哪一個演算法來解決問題 呢?
③ 參考圖 2 的函式 。假設陣列 的大小為 ,函式 的執行時間為 。 的執行時間為 。推導出函數 ; 必須是最簡型式。
註:每一小題都必須寫出推導過程;若直接猜答案,沒有推導過程,該小題以 0 分計算。
// Function F1
void F1(int A[], int first, int last) {
if (first < last) {
int middle = (first + last) / 2;
F1(A, first, middle);
F1(A, middle + 1, last);
F2(A, first, last); // Assume F2 has a time complexity of O(n)
}
}
圖 2: 用 C/C++ 語言所撰寫的函式 F1
登入後即可作答並保存紀錄。
① 調和級數的漸近時間
核心觀念: 使用調和級數的漸近界。依掃描圖,執行時間為
解題方法: 以積分估計調和級數:
因此 。對數底數只會造成常數倍差異,不影響漸近界;最簡型式可寫成 。
解題技巧: 遇到 ,可直接辨認為調和級數,其漸近成長為對數級。
② 比較兩個演算法的時間
核心觀念: 比較漸近成長率;輸入規模夠大時,成長較慢的執行時間較佳。
解題方法: 依掃描圖中的公式,演算法 的時間為 ,演算法 的時間為 。先化簡 :
比較兩者的比值:
所以 成長得比 快;輸入規模夠大時,應選擇演算法 。
解題技巧: 先利用 化簡,再比較兩式的成長率。
第 3 題10 分
【此題 10 分】給定 、、、、,求出下列後序 (postfix) 運算式的計算結果。註:此題必須要有推導過程;若直接猜答案,此題以 0 分計算。
登入後即可作答並保存紀錄。
核心觀念
後序式(postfix)把運算子放在運算元之後。由左至右讀取時,遇到數值就放入堆疊;遇到二元運算子,就取出堆疊頂端的兩個數值運算,再把結果放回堆疊。
依原卷圖中的式子,運算式為 。
解題方法
代入 、、、、,逐步處理:
| 讀取內容 | 操作 | 堆疊(底部 → 頂端) |
|---|---|---|
| 放入 | ||
| 放入 | ||
| 放入 | ||
| 計算 |
第 4 題10 分
【此題 10 分】假設陣列 裡的元素皆為正整數,且最多能容納 個元素,但目前只儲存了 個元素,其中 。假設陣列 裡的元素已經排序,那麼給定正整數 ,我們可以使用 binary search 在 的時間內判定陣列 裡頭是否有元素 。然而,在已排序的陣列裡頭,即使不考慮搜尋時間,插入或刪除一個元素都必須耗時 。我們知道,在 doubly linked list 裡頭,在不考慮搜尋時間的情況下,插入或刪除一個元素都只耗時 。給定正整數 ,如果我們在已排序的 doubly linked list 裡頭使用 binary search,能否在 的時間內判定 裡頭是否有元素 ?先回答你的答案,然後才解說你的立論根據;若只是猜答案,未提供任何解說,此題以 0 分計算。
登入後即可作答並保存紀錄。
核心觀念
陣列支援隨機存取,可在 時間直接取得中間位置的元素;雙向鏈結串列則只能沿著前後指標逐節點移動,沒有辦法在 時間跳到中間位置。二分搜尋要達到 ,除了比較次數是 ,每次也必須能有效取得搜尋區間的中間元素。
解題方法
答案:不能在 時間內判定。
若要在 中照二分搜尋的方式找中間節點,必須先從串列的一端逐節點走到中間,這一步就需要 時間。接著每次縮小搜尋範圍時,仍須沿著指標移動到新的中間節點。
第 5 題20 分
Graph 是由點 (node) 和邊 (edge) 所構成的集合。圖 3 為一 undirected graph 的 adjacency-lists,裡頭的數字為 node 的 ID (編號)。
① 寫出執行 depth-first search,從 node 0 開始拜訪所有 nodes 所依序產生的 node 編號。
② 寫出執行 breadth-first search,從 node 0 開始拜訪所有 nodes 所依序產生的 node 編號。
注意:當一個 node 有多個鄰居 (neighboring nodes) 時,不能按任意次序拜訪,必須按 adjacency-list 裡頭的次序拜訪。
🖼️【此處有附圖,請對照原卷】
圖 3: undirected graph 的 adjacency-lists,其中數字表示 node 的編號、「/」表示 NULL
登入後即可作答並保存紀錄。
本題考查圖的兩種基本遍歷演算法:深度優先搜尋 (Depth-First Search, DFS) 和廣度優先搜尋 (Breadth-First Search, BFS)。題目提供了圖的鄰接串列 (adjacency list) 表示法,並指定了遍歷的起始點 (node 0) 和鄰居的拜訪順序規則。
圖的結構分析:
根據圖 3 的 adjacency-lists,我們可以得到節點的鄰居關係:
- Node 0: neighbors are 1, 5. (Visit order: 1 then 5)
- Node 1: neighbors are 0, 2, 3. (Visit order: 0 then 2 then 3)
- Node 2: neighbors are 1, 4. (Visit order: 1 then 4)
- Node 3: neighbors are 1, 4. (Visit order: 1 then 4)
- Node 4: neighbors are 2, 3, 6. (Visit order: 2 then 3 then 6)
- Node 5: neighbors are 0, 4. (Visit order: 0 then 4)
- Node 6: neighbors are 4, 5. (Visit order: 4 then 5)
遍歷規則:
- 起始點:Node 0。
- 拜訪順序:必須嚴格按照 adjacency-list 中列出的順序。
- DFS:使用堆疊 (stack) 或遞迴實現。當訪問一個節點時,標記為已訪問,然後遍歷其鄰居。如果鄰居未被訪問,則遞迴地訪問該鄰居。
- BFS:使用佇列 (queue) 實現。當訪問一個節點時,標記為已訪問,並將其加入佇列。然後從佇列中取出節點,遍歷其鄰居。如果鄰居未被訪問,則標記為已訪問並加入佇列。
① 深度優先搜尋 (DFS) 的拜訪順序:
我們從 node 0 開始。使用遞迴或堆疊來模擬 DFS。
-
Visit 0: Mark 0 as visited. Add 0 to the result list.
Result: [0]
Neighbors of 0: 1, 5. Visit 1 first. -
Visit 1: Mark 1 as visited. Add 1 to the result list.
Result: [0, 1]
Neighbors of 1: 0, 2, 3. Node 0 is visited. Visit 2 next. -
Visit 2: Mark 2 as visited. Add 2 to the result list.
Result: [0, 1, 2]
Neighbors of 2: 1, 4. Node 1 is visited. Visit 4 next. -
Visit 4: Mark 4 as visited. Add 4 to the result list.
Result: [0, 1, 2, 4]
Neighbors of 4: 2, 3, 6. Node 2 is visited. Visit 3 next. -
Visit 3: Mark 3 as visited. Add 3 to the result list.
Result: [0, 1, 2, 4, 3]
Neighbors of 3: 1, 4. Node 1 is visited. Node 4 is visited. No unvisited neighbors from 3. Backtrack to 4. -
Backtrack from 3 to 4. All neighbors of 4 (2, 3) have been visited. Backtrack to 2.
-
Backtrack from 4 to 2. All neighbors of 2 (1, 4) have been visited. Backtrack to 1.
-
Backtrack from 2 to 1. Next neighbor of 1 is 3. Node 3 is already visited. No more unvisited neighbors from 1. Backtrack to 0.
-
Backtrack from 1 to 0. Next neighbor of 0 is 5. Visit 5.
-
Visit 5: Mark 5 as visited. Add 5 to the result list.
Result: [0, 1, 2, 4, 3, 5]
Neighbors of 5: 0, 4. Node 0 is visited. Node 4 is visited. No unvisited neighbors from 5. Backtrack to 0.