112 年 國立中山大學電機工程學系碩士班丙組《資料結構》
第 1 題
【此題30分;每一小題10分】
- 我們用 表示演算法A的input size,用 表示演算法A的執行時間。假設 的時間複雜度(time complexity)為 。寫出 的數學定義。
- 假設演算法 B 和演算法 C 都能解決問題 Q, 但演算法 B 的執行時間為 , 演算法 C 的執行時間為 , 其中 為 input size。假設 的值很大, 並且我們用時間複雜度來判斷演算法的好壞, 那我們該選用哪一個演算法來解決問題 Q 呢? 解釋你的立論根據; 直接寫答案, 此一小題以0分計算。
- 圖1 是使用跟「C 程式語言」類似的語法描述 insertion sort, 其中 表示陣列的名稱, 其元素皆為整數, 而 為陣列裡頭的元素數量。此一 insertion_sort 函式(function)能夠將陣列裡的元素由小到大排序。我們用 表示 insertion_sort 函式的執行時間。那麼「」這樣的說法是否正確? 解釋你的立論根據; 若未解釋, 此小題以0分計算。
void insertion_sort(int D[], int n) {
for (int i = 1; i < n; i++) {
int key = D[i];
int j = i - 1;
while ((j >= 0) and (D[j] > key)) {
D[j + 1] = D[j];
j--;
}
D[j + 1] = key;
}
}
圖1: insertion_sort 函式。
登入後即可作答並保存紀錄。
核心觀念
本題考查漸近緊確界 的定義、不同成長率的比較,以及插入排序的執行時間如何受輸入排列影響。
解題方法
1. 的數學定義
若存在正常數 與正整數 ,使得所有 都滿足
則稱 。
也就是說,當 充分大時, 同時有 的漸近下界與上界,兩者只差正常數倍。
2. 比較演算法 B 與 C
依照考卷原圖,演算法 B 的時間複雜度為 ,演算法 C 為 。
令 。此時 可寫成 。由 Stirling 公式,
當 趨近無窮大時, 的成長速度高於任何固定常數倍的 ,因此 最終會比 成長得快。
題目以執行時間較低作為較好的判斷標準,所以應選漸近成長較慢的演算法 B。
3. 判斷插入排序是否為
第 2 題
【此題25分;第①小題15分,第②小題10分】
假設一個 binary tree, 其 preorder 的拜訪次序為 A→B→D→G→E→C→F, 其 inorder 的拜訪次序為 G→D→B→E→A→C→F。
① 請畫出此一 binary tree。註:必須寫出你推導出這棵 binary tree的過程。直接寫答案,此小題以0分計算。
② 針對此一 binary tree, 寫出 postorder 的拜訪次序。此小題可以直接寫答案。
登入後即可作答並保存紀錄。
核心觀念
前序走訪順序為「根 → 左子樹 → 右子樹」,中序走訪順序為「左子樹 → 根 → 右子樹」,後序走訪順序為「左子樹 → 右子樹 → 根」。
重建二元樹時,先用前序序列的第一個節點找出根,再從中序序列定位根。根左側是左子樹,根右側是右子樹;依此分割前序序列後,對左右子樹遞迴處理。
解題方法
- 前序序列第一個節點是根,因此根為 。在中序序列 中, 左側的 屬於左子樹,右側的 屬於右子樹。
- 左子樹的前序序列為 。其根為 ;在左子樹的中序序列 中, 左側是 ,右側是 。因此 的左子樹由 組成,右子節點為 。
- 的左子樹前序序列為 ,故根為 ;
第 3 題
【此題15分】針對下列資料序列(sequence)
16、30、8、79、24、51
使用 heap sort 進行排序,並使得輸出結果為「由小到大」的資料序列。註:此題必須畫出排序的每個步驟;若步驟有省略,將視情況予以適量扣分。若直接寫答案,此題以0分計算。
登入後即可作答並保存紀錄。
核心觀念
Heap sort(堆積排序)將序列視為完全二元樹,並利用堆積的性質排序。
以第 格為根節點時:
最大堆積(max heap)的定義為:每個父節點皆不小於其子節點。因此,根節點必定是目前堆積中的最大值。
題目要求由小到大排序,採用最大堆積。每一輪將根節點的最大值與未排序區最後一個元素交換,該最大值即固定在序列尾端,再對剩餘部分向下調整。
解題方法
原始序列為:
其完全二元樹如下:
16
/ \
30 8
/ \ /
79 24 51
一、建立最大堆積
最後一個非葉節點為:
因此從第 格、第 格、第 格依序向上調整。
步驟 1:調整第 3 格
第 3 格為 ,其子節點為第 6 格的 ,交換:
步驟 2:調整第 2 格
第 2 格為 ,子節點為 ,與較大的子節點 交換:
步驟 3:調整第 1 格
第 1 格為 ,子節點為 ,先與較大的 交換:
此時 下移至第 2 格,其子節點為 ,再與較大的 交換:
最大堆積建立完成:
79
/ \
30 51
/ \ /
16 24 8
二、反覆取出最大值
記號
豎線左側才參與後續堆積調整。
第 1 輪:堆積大小由 6 變為 5
將根節點 與第 6 格的 交換:
第 4 題
【此題20分;每一小題10分】
① 圖2為一棵AVL tree。在圖2裡頭插入「73」之後的AVL tree為何?請畫出來。
② 承上題,在插入「73」之後,接著刪除「28」所產生的AVL tree為何?請畫出來。
🖼️【此處有附圖,請對照原卷】
圖2: AVL tree。
登入後即可作答並保存紀錄。
核心觀念
本題考查 AVL tree 的兩項操作:
- 插入節點後,利用平衡因子判斷是否失衡,並進行旋轉。
- 刪除節點後,重新計算各祖先節點的平衡因子,必要時進行旋轉。
平衡因子定義為:
AVL tree 必須滿足:
題目資訊不足
目前附上的圖片是「控制系統」考題第 2 頁,圖中沒有本題所需的「圖 2 AVL tree」。因此無法得知原始 AVL tree 的節點配置,也就無法唯一確定插入 及刪除 後的兩棵樹。
解題方法
① 插入
依照二元搜尋樹規則,從根節點開始比較:
- 大於目前節點,往右子樹走。
- 小於目前節點,往左子樹走。
- 找到空位置後插入葉節點 。
接著由插入位置向根節點回溯,更新高度並計算每個祖先的平衡因子。
第 5 題10 分
圖 3 為「雙向環狀串列(doubly circular linked list,或者簡稱 doubly circular list)」的資料結構範例,其中每個 Node 的結構包含三個欄位「prev、data、next」。更具體地說,Node 的結構宣告如下:
struct Node {
Node* prev; // 指向前一個 Node
int data; // 用來儲存整數型別的資料
Node* next; // 指向下一個 Node
};
🖼️【此處有附圖,請對照原卷】
為了實現雙向環狀串列,我們用 C++ 程式語言宣告一個類別(class),如下:
class DoublyCircularList {
private:
int numberOfNodes; // 紀錄雙向環狀串列裡頭的 Node 數量
Node* head; // 此一指標變數永遠指向雙向環狀串列裡頭的第一個 Node
Node* tail; // 此一指標變數永遠指向雙向環狀串列裡頭的最後一個 Node
public:
DoublyCircularList() { // 此為預設建構式(default constructor)
numberOfNodes = 0;
head = tail = nullptr;
// nullptr 是 C++ 的關鍵字,是 null pointer 的意思
}
// 其餘省略
};
DoublyCircularList 類別裡頭包含二個指標變數:head 和 tail。如圖 3 所示,指標變數 head 永遠指向串列裡頭的第一個 Node,指標變數 tail 永遠指向串列裡頭的最後一個 Node。
請幫 DoublyCircularList 類別撰寫一個成員函式(member function),其宣告為 void removeFrontNode(),其功能是將 head 指標變數所指到的 Node 刪除。
注意:此題限定使用 C++ 程式語言撰寫成員函式,使用其他程式語言或 pseudo code,將酌情扣分。
登入後即可作答並保存紀錄。
核心觀念
雙向環狀串列的每個節點都有 prev 與 next 指標。若串列非空,首尾相接:head->prev 指向 tail,tail->next 指向 head。刪除頭節點時,要更新新頭節點、尾節點的環狀連結,並釋放舊頭節點的記憶體。
解題方法
圖 3 中節點依序為 31 → 25 → 79 → 64,head 指向 31,tail 指向 64;環狀連結為 64.next = 31、31.prev = 64。刪除 head 時,分別處理空串列、只有一個節點,以及至少兩個節點的情況。