113 年 國立中山大學電機工程學系碩士班丙組《資料結構》

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

第 1 題15 分

圖1是用類似 C/C++ 語言所撰寫的函式 (function),名為 maxHeapify,功能如下:假設 max-heap 裡頭有 nn 個點,每個點的內容皆為正整數,儲存在陣列 AA 裡頭 A[0]A[0] 至 A[n−1]A[n-1] 的地方。假設以 A[i]A[i] 的左小孩為 root 的 subtree 已經是 max-heap、以 A[i]A[i] 的右小孩為 root 的 subtree 也已經是 max-heap,但 A[i]A[i] 可能違反 max-heap order property;在這種情況下呼叫函式 maxHeapify(A, i, n),可以使得以 A[i]A[i] 為 root 的 subtree 成為 max-heap。

現在,給定陣列 BB,裡頭有個元素,皆為正整數,儲存在 B[0]B[0] 至 B[n−1]B[n-1] 的地方;陣列 BB 裡頭的這個元素沒有任何規律。限定必須呼叫 maxHeapify 函式,並以 recursive 的方式撰寫 buildMaxHeap 函式,使得當我們呼叫 buildMaxHeap(B, 0, n) 時,便能將具有 nn 個元素的陣列 BB 調整成為 root 為 B[0]B[0] 的 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 函式

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

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

這一題的完整詳解

核心觀念

maxHeapify(A, i, n) 的前提是:

  • 以左小孩為根的 subtree 已經是 max-heap。
  • 以右小孩為根的 subtree 已經是 max-heap。
  • 只有 A[i]A[i] 可能違反 max-heap order property。

因此,buildMaxHeap 必須採用「後序」處理順序:

  1. 先將左 subtree 建成 max-heap。
  2. 再將右 subtree 建成 max-heap。
  3. 最後呼叫 maxHeapify(B, i, n),使以 B[i]B[i] 為根的 subtree 成為 max-heap。

陣列採用 0-based indexing 時:

leftChild(i)=2i+1\text{leftChild}(i)=2i+1 rightChild(i)=2i+2\text{rightChild}(i)=2i+2

完全二元樹中,索引 i≥⌊n/2⌋i \ge \lfloor n/2 \rfloor 的節點沒有小孩,因此是葉節點,本身已經是 max-heap。

解題方法

從節點 ii 開始遞迴處理:

  • 若 i≥⌊n/2⌋i \ge \lfloor n/2 \rfloor,代表目前節點是葉節點,直接結束。
  • 否則先遞迴處理左小孩。
  • 再遞迴處理右小孩。
  • 左右 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 分

① 假設演算法的執行時間為 T(n)=1n+12+13+⋯+1n=∑i=1n1iT(n) = \frac{1}{n} + \frac{1}{2} + \frac{1}{3} + \dots + \frac{1}{n} = \sum_{i=1}^{n} \frac{1}{i},其中 n≥1n \ge 1 為演算法的 input size。求出 f(n)f(n)。注意:f(n)f(n) 必須是最簡 (simplest) 型式。

② 假設演算法 AA 和演算法 BB 都能解決問題 QQ,其中演算法 AA 的執行時間為 nlog⁡2nn\log_2 n,演算法 BB 的執行時間為 log⁡2(nk)\log_2 (n^k),其中 nn 為 input size。假設 nn 的值很大,並且我們用時間複雜度來判斷演算法的好壞,那麼我們該選用哪一個演算法來解決問題 QQ 呢?

③ 參考圖 2 的函式 F1F1。假設陣列 AA 的大小為 nn,函式 F2(A,0,n−1)F2(A, 0, n-1) 的執行時間為 Θ(n)\Theta(n)。F1(A,0,n−1)F1(A, 0, n-1) 的執行時間為 Θ(f(n))\Theta(f(n))。推導出函數 f(n)f(n);f(n)f(n) 必須是最簡型式。

註:每一小題都必須寫出推導過程;若直接猜答案,沒有推導過程,該小題以 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 頁

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

這一題的完整詳解

① 調和級數的漸近時間

核心觀念: 使用調和級數的漸近界。依掃描圖,執行時間為

T(n)=∑i=1n1i=Hn.T(n)=\sum_{i=1}^{n}\frac{1}{i}=H_n.

解題方法: 以積分估計調和級數:

ln⁡(n+1)≤∑i=1n1i≤1+ln⁡n.\ln(n+1)\leq \sum_{i=1}^{n}\frac{1}{i}\leq 1+\ln n.

因此 T(n)=Θ(log⁡n)T(n)=\Theta(\log n)。對數底數只會造成常數倍差異,不影響漸近界;最簡型式可寫成 f(n)=log⁡nf(n)=\log n。

解題技巧: 遇到 ∑i=1n1/i\sum_{i=1}^{n}1/i,可直接辨認為調和級數,其漸近成長為對數級。

② 比較兩個演算法的時間

核心觀念: 比較漸近成長率;輸入規模夠大時,成長較慢的執行時間較佳。

解題方法: 依掃描圖中的公式,演算法 AA 的時間為 nlog⁡2nn^{\log_2 n},演算法 BB 的時間為 log⁡2(nn)\log_2(n^n)。先化簡 BB:

TB(n)=log⁡2(nn)=nlog⁡2n.T_B(n)=\log_2(n^n)=n\log_2 n.

比較兩者的比值:

TA(n)TB(n)=nlog⁡2nnlog⁡2n=nlog⁡2n−1log⁡2n⟶∞.\frac{T_A(n)}{T_B(n)} = \frac{n^{\log_2 n}}{n\log_2 n} = \frac{n^{\log_2 n-1}}{\log_2 n} \longrightarrow \infty.

所以 TA(n)T_A(n) 成長得比 TB(n)T_B(n) 快;輸入規模夠大時,應選擇演算法 BB。

解題技巧: 先利用 log⁡(ab)=blog⁡a\log(a^b)=b\log a 化簡,再比較兩式的成長率。

🔒

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

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

免費註冊

第 3 題10 分

【此題 10 分】給定 A=3A=3、B=2B=2、C=5C=5、D=4D=4、E=6E=6,求出下列後序 (postfix) 運算式的計算結果。註:此題必須要有推導過程;若直接猜答案,此題以 0 分計算。

CDE+ABC+∗−CDE+ABC+*-

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

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

這一題的完整詳解

核心觀念

後序式(postfix)把運算子放在運算元之後。由左至右讀取時,遇到數值就放入堆疊;遇到二元運算子,就取出堆疊頂端的兩個數值運算,再把結果放回堆疊。

依原卷圖中的式子,運算式為 C D E − + A B C + ∗ −C\ D\ E\ -\ +\ A\ B\ C\ +\ *\ -。

解題方法

代入 A=3A=3、B=2B=2、C=5C=5、D=4D=4、E=6E=6,逐步處理:

讀取內容操作堆疊(底部 → 頂端)
CC放入 55[5][5]
DD放入 44[5,4][5,4]
EE放入 66[5,4,6][5,4,6]
−-計算 4−6=−24-6=-2[5,−2][5,-2]
🔒

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

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

免費註冊

第 4 題10 分

【此題 10 分】假設陣列 AA 裡的元素皆為正整數,且最多能容納 mm 個元素,但目前只儲存了 nn 個元素,其中 n<mn < m。假設陣列 AA 裡的元素已經排序,那麼給定正整數 kk,我們可以使用 binary search 在 O(log⁡n)O(\log n) 的時間內判定陣列 AA 裡頭是否有元素 kk。然而,在已排序的陣列裡頭,即使不考慮搜尋時間,插入或刪除一個元素都必須耗時 O(n)O(n)。我們知道,在 doubly linked list 裡頭,在不考慮搜尋時間的情況下,插入或刪除一個元素都只耗時 O(1)O(1)。給定正整數 kk,如果我們在已排序的 doubly linked list LL 裡頭使用 binary search,能否在 O(log⁡n)O(\log n) 的時間內判定 LL 裡頭是否有元素 kk?先回答你的答案,然後才解說你的立論根據;若只是猜答案,未提供任何解說,此題以 0 分計算。

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

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

這一題的完整詳解

核心觀念

陣列支援隨機存取,可在 O(1)O(1) 時間直接取得中間位置的元素;雙向鏈結串列則只能沿著前後指標逐節點移動,沒有辦法在 O(1)O(1) 時間跳到中間位置。二分搜尋要達到 O(log⁡n)O(\log n),除了比較次數是 O(log⁡n)O(\log n),每次也必須能有效取得搜尋區間的中間元素。

解題方法

答案:不能在 O(log⁡n)O(\log n) 時間內判定。

若要在 LL 中照二分搜尋的方式找中間節點,必須先從串列的一端逐節點走到中間,這一步就需要 O(n)O(n) 時間。接著每次縮小搜尋範圍時,仍須沿著指標移動到新的中間節點。

🔒

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

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

免費註冊

第 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

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

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

這一題的完整詳解

本題考查圖的兩種基本遍歷演算法:深度優先搜尋 (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。

  1. Visit 0: Mark 0 as visited. Add 0 to the result list.
    Result: [0]
    Neighbors of 0: 1, 5. Visit 1 first.

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

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

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

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

  6. Backtrack from 3 to 4. All neighbors of 4 (2, 3) have been visited. Backtrack to 2.

  7. Backtrack from 4 to 2. All neighbors of 2 (1, 4) have been visited. Backtrack to 1.

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

  9. Backtrack from 1 to 0. Next neighbor of 0 is 5. Visit 5.

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

🔒

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

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

免費註冊

其他考古題