107 地方特考三等 資訊處理 資料結構 考古題詳解
第 一 題20 分
計算正整數 和 的最大公因數 的演算法,以類似 C 語言表示如下:
- integer gcd(a, b) {
- ;;
- while () { ;;;}
- return ;
- }
其中資料型態 integer 表示整數, 表示 除以 的餘數。請回答下列問題:
(一)請證明:輸入任意兩個正整數,此程式執行一定時間後就會停止,不會造成無窮迴圈。
(二)假設 ,請證明此程式之 while 迴圈(第 3 行)至多只會被執行 次。
登入後即可作答並保存紀錄。
核心觀念
本題考查計算機科學與離散數學中經典的歐幾里得演算法(輾轉相除法,Euclidean Algorithm)之正確性與時間複雜度分析,核心涉及以下四項重要觀念與定理:
- 除法原理(Division Algorithm):對任意整數 與正整數 ,存在唯一整數商 與餘數 ,使得 ,且餘數滿足 。在程式語言中,。
- 良序原理(Well-Ordering Principle):非負整數集合的任意非空子集必有最小元素。若一組非負整數序列嚴格遞減,則其長度必為有限,此乃證明迴圈終止性(Termination)的基本工具。
- 餘數減半引理(Modulo Halving Lemma):若 ,則 。此幾何級數縮減特性是歐幾里得演算法具備對數級時間複雜度 的核心數學基礎。
- 狀態遞迴與分情況討論:追蹤迴圈每回合變數的轉移,利用奇數步與偶數步的嚴格不等式,精確推導出迴圈執行的上限次數。
解題方法
(一)證明演算法必於有限時間內停止(不會有無窮迴圈)
切入點:定義迴圈狀態序列,證明控制變數 為「嚴格遞減且具有下界的非負整數序列」。
- 定義變數狀態:
設程式輸入任意正整數 。進入
while迴圈前,初始狀態為: 若迴圈執行第 次(),設進入該次迴圈時的變數值為 與 。 - 單步狀態轉移: 依程式第 3 行邏輯: 因為迴圈進入條件為 ,根據整數除法原理,餘數 必定滿足: 因此,下一輪的控制變數 滿足:
- 終止性推論:
序列 是一組由正整數出發的嚴格遞減非負整數序列:
由於 ,且每次迭代至少減少 (即 ),根據整數的良序原理, 不可能無限遞減,至多經過 次迭代後必有某個整數步 使得 。
當 時,迴圈判斷條件
y > 0為偽(False),迴圈即刻跳出並執行return x;終止程式。 得證:輸入任意兩正整數,此程式必在有限步後停止,絕不會造成無窮迴圈。
(二)證明若 ,while 迴圈至多執行 次
切入點:先建立「兩次迭代後變數規模至少減半」的重要引理,再針對迴圈總執行次數的奇偶性進行分類證明。
【引理】若整數 ,則 。
證明: 設 。依除法原理,存在整數商 使得 ,其中 。
- 狀況一:若 ,因 ,故顯然有 。
- 狀況二:若 ,則商數 (因為若 ,則 ,矛盾)。 此時 。 因為 ,所以 ,得: 綜合以上兩種狀況,對任意整數 ,恆有 ,引理成立。
【主證明】
- 變數跨步收縮關係: 已知初始 ,。
- 第 1 次執行完畢後:,。
- 第 2 次執行完畢後:,。 因為 ,由引理可知: 同理,對任意整數 ,在第 次迭代中: 且 。 因為 ,套用引理可得: 由此遞迴關係,特別觀察奇數項:
第 二 題20 分
給定一個權重圖(weighted graph),假設 ,且每個邊(edge) 的權重 都是正整數。令 為以 為端點的所有邊中權重最小的邊。將這些邊集合起來稱作 ,也就是:
(一)假設每個邊的權重都不相同。請證明由 中這些邊所構成的子圖(edge-induced subgraph) 沒有迴圈。
(二) 是否一定是 的擴張樹(spanning tree)?若是,請證明之;若不一定是,請給一個反例。
(三)用以上之結論,設計一個計算 的最小權重擴張樹(minimum spanning tree)的演算法。
(四)在一般的應用中,邊的權重可能會相同,請修正上述之演算法,使修正後之演算法可以正確找出答案。
登入後即可作答並保存紀錄。
核心觀念
- 割性質(Cut Property)與最小邊:在連通圖中,對任意非空的頂點真子集 ,跨越割 且權重嚴格最小的邊,必定包含於圖的最小權重擴張樹(Minimum Spanning Tree, MST)中。對於單一頂點 ,以其為割 ,其最小事件邊 必然是該割上的最小邊,故 必定屬於 MST。
- 波魯夫卡演算法(Borůvka's Algorithm)核心精神:本題的邊集合 即為 Borůvka 演算法在第一輪(第一階段)所挑選的邊集合。若權重互異,所選取的邊必不包含迴圈且均屬於唯一存在的 MST。
- 極值原理與反證法(Proof by Contradiction):證明無迴圈時,利用迴圈中必然存在「權重嚴格最大之邊」的性質導出矛盾。
- 全序關係建立(Tie-Breaking):當邊的權重相同時,為避免演算法產生環路或歧義,可透過給予每條邊唯一編號(或端點座標字典序)建立嚴格全序關係(Strict Total Order)。
解題方法
(一)證明 沒有迴圈(Acyclic)
採用反證法(Proof by Contradiction):
-
假設反面成立: 假設由邊集合 所導出的子圖 存在至少一個簡單迴圈 。 設該迴圈由 個相異頂點依序構成:,其中 。
-
找出迴圈中的最大邊: 因為題設「每個邊的權重都不相同」,迴圈 上的所有邊權重皆互異,因此在迴圈 中必定存在唯一的「嚴格最大權重邊」。 設此邊為 。
-
導出矛盾:
- 根據 的定義,,表示 必定是其端點 或 的最小事件邊(亦即 或 )。
- 不失一般性,假設 。這意味著在所有連接至頂點 的邊當中, 的權重嚴格最小。
- 然而,由於頂點 位於迴圈 上, 在迴圈中必定還連接了另一條相鄰邊 ()。
- 根據 為迴圈 中嚴格最大邊的設定,必有:
- 此不等式與「 是以 為端點中權重最小的邊」產生矛盾。
- 結論: 假設不成立,故 絕不可能存在迴圈, 為一個無向無環圖(即森林,Forest)。
(二)判斷 是否一定是 的擴張樹,並給出論證
不一定是 的擴張樹。
擴張樹必須滿足「連通且無迴圈」之條件。雖然由第(一)小題已知 無迴圈,但 不一定連通,其連通元件(Connected Components)數量可能大於 1,此時 僅為擴張森林(Spanning Forest),而非單一擴張樹。
- 反例說明: 考慮一包含 4 個頂點的連通圖 ,其中:
- 頂點集合:
- 邊集合與權重:
- ,權重
- ,權重
- ,權重
- ,權重
所有邊的權重皆互不相同,為正整數。
- 求各頂點的最小事件邊 :
- 對頂點 1:相鄰邊為 [權重 1] 與 [權重 20],最小為 。
- 對頂點 2:相鄰邊為 [權重 1] 與 [權重 10],最小為 。
- 對頂點 3:相鄰邊為 [權重 2] 與 [權重 10],最小為 。
- 對頂點 4:相鄰邊為 [權重 2] 與 [權重 20],最小為 。
- 分析 與 : 中只包含 2 條邊,而 4 個頂點的擴張樹必須恰有 條邊。在 中,頂點 與頂點 分屬兩個互不相連的連通分量,圖形並不連通,因此 不是 的擴張樹。
(三)利用上述結論設計最小權重擴張樹(MST)演算法
根據割性質(Cut Property),每個頂點所挑選的最小邊 必定屬於最小權重擴張樹。由於一次挑選可能形成多個連通元件(森林),可將各連通元件視為「超頂點(Super-vertex)」,反覆套用此選邊策略進行元件縮合。這正是經典的 Borůvka 演算法(又稱 Sollin 演算法)。
演算法步驟:
- 初始化:
- 令擴張樹邊集合 。
- 使用互斥集合(Disjoint Set / Union-Find)資料結構,將圖中每一個頂點 各自初始化為一個獨立的連通元件。
第 三 題20 分
假設陣列 儲存 個正整數 。
(一)已知所有的正整數 。請設計一個 時間的演算法,將這些整數由小到大排列。
(二)已知所有的正整數 。請設計一個 時間的演算法,將這些整數由小到大排列,或證明這是不可行的。
登入後即可作答並保存紀錄。
核心觀念
- 比較排序法與非比較排序法的時間複雜度界限:
- 一般基於「兩兩比較」的排序演算法(如 Merge Sort、Heap Sort),其時間複雜度下限為 。
- 若欲在線性時間 或 內完成排序,必須打破比較排序的限制,改採非比較排序法(Non-comparison Sort),如計數排序法(Counting Sort)與基數排序法(Radix Sort)。
- 計數排序法(Counting Sort):
- 適用於已知輸入為介於特定範圍內的整數。若數值範圍為 至 ,建立大小為 的計數陣列統計各數值的出現頻率,並利用累加次數(前綴和)決定各元素的輸出位置。
- 時間複雜度為 ,空間複雜度為 ,且具有穩定性(Stability)。
- 基數排序法(Radix Sort):
- 依據位數由低位至高位(LSD, Least Significant Digit first)依序進行排序,每一輪位數的排序必須使用穩定的排序演算法(通常採用計數排序)。
- 若 個鍵值的最大值為 ( 為常數),只要選擇以 為基底(Base-)進制表示,各數值的位數即為 位。每位數的值域為 ,每輪計數排序耗時 ,總時間複雜度為 。
解題方法
(一) 的 排序演算法
本小題直接採用計數排序法(Counting Sort)。
演算法步驟
- 配置計數陣列:宣告一個大小為 的陣列 ,並將所有元素初始化為 。配置與原陣列大小相同的輸出陣列 。
- 統計出現次數:走訪陣列 ,依元素值統計各數出現次數:
- 計算前綴累加和:將 陣列轉換為小於或等於該數值的累積出現次數: 此時 即代表數值 在排序後輸出陣列中的最終存放位置(1-based)。
- 輸出排序結果(確保穩定性):從原陣列 的尾端向前端反向走訪( 倒數至 ):
- 將 複製回 (或直接輸出 )。
虛擬碼(Pseudocode)
Algorithm COUNTING-SORT(A, n, M)
let C[1..M] be a new array, initialized to 0
let B[1..n] be a new array
for i = 1 to n do
C[A[i]] = C[A[i]] + 1
for j = 2 to M do
C[j] = C[j] + C[j - 1]
for i = n downto 1 do
B[C[A[i]]] = A[i]
C[A[i]] = C[A[i]] - 1
return B
複雜度分析
- 初始化陣列 :耗時 。
- 頻率統計:走訪長度為 的陣列,耗時 。
- 累加前綴和:走訪長度為 的陣列,耗時 。
- 反向輸出定位:走訪長度為 的陣列,耗時 。
- 總時間複雜度:。
- 輔助空間複雜度:(用於陣列 與 )。
(二) 的 排序演算法
此要求完全可行。演算法採用以 為基底的基數排序法(Base- LSD Radix Sort),內部搭配計數排序法。
核心推導與數學轉換
若直接使用第(一)小題的計數排序法,因鍵值上限 ,時間複雜度將膨脹為 ,不符要求。因此必須將數值降維分解:
- 原整數值域為 。為便於進位表示,先平移轉換為 0-based 數值:
- 任意介於 至 的整數 ,均可精確以 2 個位數的 進位制數值 表示: 其中:
- 低位數(個位數):,其值域為 。
- 高位數(十位數):,其值域為 。
- 透過 LSD(Least Significant Digit)基數排序,僅需對低位數與高位數分別進行一次穩定的計數排序,共計 2 回合。
演算法步驟
- 前處理:對所有 至 ,設定 。
- 第一回合(排序低位數):
- 提取各元素的鍵值 。
- 由於鍵值範圍為 到 (共 種可能),以 為排序基準,對陣列執行穩定計數排序法。
- 本輪時間複雜度為 。
- 第二回合(排序高位數):
- 提取上一輪排序後陣列中各元素的鍵值 。
第 四 題20 分
假設有個陣列 儲存著 個整數。可將 看成二元樹,其中 是樹根。 的左右子節點分別為 和 ,。若 或 ,則這些子節點不存在。若 滿足:
則稱陣列 是一個堆疊(heap)。
假設有個副程式 ,其輸入參數 是一個陣列, 是 的大小, 是一個指標,指向此子樹的樹根。副程式 的功能是將以 為樹根的子樹變成 heap。在呼叫 之前,它的左右子樹都已經是 heap。
副程式 所需的計算時間是 ,其中 是以 為樹根的子樹的高度,也就是從樹根到任一樹葉的最長距離。
(一)用 設計一個線性時間的演算法,將陣列 變成 heap。
(二)分析以上所設計演算法的計算複雜度為 。
登入後即可作答並保存紀錄。
核心觀念
- 二元堆積(Binary Heap)之陣列表示法: 在以 為起始索引的陣列 中,對於任意節點 :
- 其左子節點為 ,右子節點為 。
- 其父節點為 。
- 葉節點(Leaf nodes)的索引範圍為 至 ;非葉節點(內部節點)的索引範圍為 至 。
- 題目中的「堆疊(heap)」係指資料結構中的「最大堆積(Max-Heap)」,滿足父節點數值大於等於子節點數值之偏序關係:。
- 自底向上建立堆積(Bottom-Up Heap Construction / Floyd's Build-Heap):
- 副程式 運作的前提是:以 為根的左右子樹皆已滿足堆積性質。
- 葉節點本身無子節點,自然滿足堆積性質,因此不需要進行 。
- 演算法應自最後一個非葉節點 開始,依序遞減至樹根 ,逐一呼叫 ,確保每次呼叫時,其左右子樹均已由先前的步驟調整為合法堆積。
- 計算複雜度之級數求和:
- 傳統直覺常誤認為呼叫約 次 、每次花費 ,導致估計為 。
- 精確分析應將「節點所在的高度」與「該高度的節點數量」合併考慮。越接近底層的節點數量越多,但下沉高度極小;樹根節點下沉高度最大,但只有 個節點。透過無窮差比級數求和,可嚴格證明其總時間為線性時間 。
解題方法
(一)線性時間建堆演算法設計
演算法說明:
- 陣列中索引為 至 的元素均為葉節點,其高度為 ,其各自形成的單節點子樹已經符合堆積定義。
- 我們由最後一個內部節點 遞減迴圈至樹根 。
- 當處理到節點 時,其左子節點 與右子節點 (若存在)的索引均大於 。由於迴圈由大到小執行,以 和 為根的子樹已在先前的步驟中被轉化為合法堆積,完全滿足呼叫 的前置條件。
- 當 執行完畢後,整個陣列 即成為合法堆積。
演算法虛擬碼(Pseudocode):
Algorithm Build-Heap(A, n)
Input:
A: 儲存 n 個整數的陣列 A[1..n]
n: 陣列的大小
Output:
符合堆積(Max-Heap)性質的陣列 A[1..n]
begin
for i := floor(n / 2) down to 1 do
sift(A, i, n);
end for
end
(二)計算複雜度為 之嚴格推導
1. 節點高度與數量界限:
- 定義節點的高(height) 為從該節點往下到達任一葉節點的最長路徑長度(邊數)。
- 葉節點的高度為 。
- 整個二元樹的樹高為 。
- 在一個包含 個節點的完全二元樹中,高度為 的節點最多有 個。
2. 總計算時間之數學式: 副程式 處理高度為 的節點時,所需時間正比於其高度,即花費 ( 為某一常數)。 葉節點()不需要執行 。總時間 為所有非葉節點執行 所需時間的總和:
第 五 題20 分
斐波納契數(Fibonacci number) 的定義是:
計算 Fibonacci number 的演算法,以類似 C 語言表示如下:
-
integer ; // array of integers
-
integer {
-
if ()
-
;
-
return ;
-
}
-
integer Fib(n) {
-
;;
-
for (;;) 10. ; 11. return ;
-
}
其中資料型態 integer 表示整數。假設輸入的整數 。主程式執行 ,則副程式 第 4 行之指令:
會被執行幾次?請說明理由。
登入後即可作答並保存紀錄。
核心觀念
本題的核心考點為動態規劃(Dynamic Programming)中的由上而下記憶化遞迴(Top-down Memoization)。
- 重疊子問題(Overlapping Subproblems):傳統樸素遞迴計算斐波納契數列時,相同子問題會被重複呼叫多次,時間複雜度高達 。
- 記憶化查表(Memoization Table):演算法配置全域陣列 作為備忘錄(Lookup Table)。
- 當 時,代表子問題 已經被計算過,直接查表回傳數值,不再深入遞迴。
- 當 (本題初值設為 )時,代表子問題 尚未被計算過,此時必須進入第 4 行執行計算,並將結果存入 。
- 關鍵指令觸發條件:第 4 行指令 僅在「首次求解狀態 」時才會被執行。
解題方法
1. 程式初始狀態分析
在主程式呼叫 時:
- 第 8 行設定基礎狀態(Base Cases):、。
- 第 9~10 行迴圈將待求的未知狀態初始化:
- 第 11 行呼叫 開始進行由上而下的記憶化搜尋。
2. 遞迴呼叫展開與執行流程
當呼叫 時,由於 ,第 4 行指令被觸發,開始評估右側運算式 。
依 C 語言由左至右評估運算元的特性:
- 左子樹遞迴優先展開: 優先呼叫 ;因為 ,第 4 行再次被觸發,轉而呼叫 ……此過程一路沿著左分支向下遞迴,直到呼叫 。 在進入 時,因 ,第 4 行執行 :
- 呼叫 :此時 ,不滿足
f[n] < 0,不執行第 4 行,直接回傳 。 - 呼叫 :此時 ,不滿足
f[n] < 0,不執行第 4 行,直接回傳 。 - 計算完成,將 寫入陣列並回傳。
- 右子樹呼叫命中快取(Cache Hit):
- 當 計算完畢並寫入 後,遞迴返回至 的第 4 行:。
- 此時呼叫右運算元 ,因 ,直接回傳,不執行第 4 行。計算完成後寫入 並回傳。
- 同理,當返回上一層 的第 4 行時,其右運算元為 ;而 早在左子樹展開時計算完畢(),因此 直接查表回傳,右子樹完全不會進入第 4 行。