109 年 國立中正大學電機工程學系碩士班計算機工程組《資料結構》
第 1 題5 分
- Heap
i. (5 points) Draw an example of a heap and define the data structure of a heap using C or pseudo code.
登入後即可作答並保存紀錄。
核心觀念
本題考查兩個重點:
- 能畫出符合條件的 Heap。
- 能用 C 語言或虛擬碼定義 Heap 的資料結構。
Heap 是一種以「完全二元樹」為基礎的樹狀資料結構,並且必須滿足堆積性質:
- Max Heap(最大堆積):每個父節點的值皆大於或等於子節點。
- Min Heap(最小堆積):每個父節點的值皆小於或等於子節點。
題目未指定種類,因此以下以最常見的 Max Heap 為例。
解題方法:畫出一個合法的 Heap
取下列 Max Heap:
90
/ \
70 60
/ \ / \
20 40 50 10
此樹符合兩項條件:
1. 完全二元樹
除了最後一層外,每一層皆填滿;最後一層的節點由左至右排列,中間沒有空缺。
本例各層節點數如下:
- 第 1 層:90
- 第 2 層:70、60
- 第 3 層:20、40、50、10
因此符合完全二元樹的形狀限制。
2. 滿足最大堆積性質
逐一比較父節點與子節點:
所有父節點皆不小於其子節點,因此這是一個合法的 Max Heap。
以陣列表示 Heap
Heap 通常不使用指標表示,而是利用陣列儲存完全二元樹。
上述 Heap 可表示為:
若採用 1-based indexing,節點位置關係為:
- 父節點位置:
- 左子節點位置:
- 右子節點位置:
陣列內容如下:
| 位置 | 值 | 父節點位置 |
|---|---|---|
| 1 | 90 | 無 |
| 2 | 70 | 1 |
| 3 | 60 | 1 |
| 4 | 20 | 2 |
| 5 | 40 | 2 |
| 6 | 50 | 3 |
| 7 | 10 | 3 |
若採用 0-based indexing,則公式改為:
C 語言資料結構定義
#define MAX_SIZE 100
typedef struct {
int data[MAX_SIZE];
int size;
} MaxHeap;
其中:
data:儲存 Heap 中的元素。size:目前 Heap 內的元素個數。data[0]:根節點,也是最大值所在位置。data[i]的子節點位置為2*i+1與2*i+2,因為此定義採用 0-based indexing。
若以 0-based indexing 儲存前述範例:
data[0] = 90
data[1] = 70
data[2] = 60
data[3] = 20
data[4] = 40
data[5] = 50
data[6] = 10
size = 7
關鍵操作的虛擬碼
插入元素
新元素必須先放在陣列尾端,以維持完全二元樹的形狀,再向上調整,恢復最大堆積性質。
第 1 題15 分
ii. (15 points) Using the data structures above, define the function to construct a heap of an integer array. Be sure to define the function name, parameter list, return value, local variables, and calling method. Use C or pseudo code to complete your answer.
登入後即可作答並保存紀錄。
題目資訊假設
題幹引用的「上述資料結構」未附於題目內容。以下採用標準的「整數陣列表示最大堆(max-heap)」定義,陣列採 0-based indexing,且函式直接在原陣列上建堆。
核心觀念
最大堆必須同時符合:
- 結構是一棵完全二元樹。
- 每個節點的值大於或等於其子節點。
對於 0-based 陣列中的節點 i:
陣列中索引為
的節點都是葉節點,因此本身已經是堆。最後一個非葉節點為:
建堆時,從最後一個非葉節點反向處理至根節點,對每個節點執行 sift-down,使其子樹符合最大堆性質。
解題方法
對索引 i 執行 sift-down:
- 比較目前節點與左、右子節點。
- 找出三者中值最大的節點。
- 若最大值不是目前節點,交換兩者。
- 將處理位置向下移動,持續修復受影響的子樹。
之所以必須由下往上處理,是因為處理節點 i 時,其左、右子樹已經先被建成最大堆,此時只需把 i 的值向下調整即可。
C 程式碼
第 1 題15 分
iii. (15 points) Analyze the time complexity of the function above and show the result in the Big-O notation. Be sure to show the steps in analysis and the associated detail calculation.
登入後即可作答並保存紀錄。
題目資訊與合理假設
題目所稱的 function above 未附在目前題幹中,因此無法對原函式給出唯一複雜度;以下假設該函式只有一個由 執行到 的迴圈,且迴圈本體每次耗時固定為 。
for (i = 1; i <= n; i++) {
// 固定時間操作
}
核心觀念
時間複雜度分析是計算基本操作隨輸入大小 增加的成長率,並忽略常數係數與低階項。
- :表示執行時間的漸近上界。
- :表示執行時間的漸近緊確界。
- 單層迴圈若執行 次,且每次執行固定時間,總時間為
解題方法與詳細計算
令每個基本操作的成本為常數:
- 初始化
i = 1:執行 次,成本為 。 - 判斷
i <= n:成功進入迴圈 次,最後失敗 次,因此共 次。 - 迴圈本體:每次執行固定時間,共 次。
i++:每次迭代執行一次,共 次。
因此總執行時間可寫成
其中 都是與 無關的常數。整理得
第 2 題5 分
- Hashing
i. (5 points) Consider to store data with integer keys. Draw an example of a hashing procedure and define the data structure to a hash table using C or pseudo code.
登入後即可作答並保存紀錄。
核心觀念
Hashing 的目的是將整數鍵值 透過雜湊函數 轉換成雜湊表中的陣列索引:
其中 為雜湊表大小。最基本的除法雜湊法為:
不同鍵值經過雜湊函數後可能得到相同索引,此現象稱為碰撞(collision)。因此,雜湊表必須指定碰撞處理方法。本題採用 separate chaining,每個陣列位置存放一條鏈結串列,所有雜湊到同一索引的資料放在同一串列中。
負整數在 C 語言中需將餘數正規化為非負值:
負責確保索引一定落在 到 之間。
解題方法與雜湊流程
令雜湊表大小為 ,採用:
依序插入鍵值:
計算結果如下:
| 鍵值 | 說明 | |
|---|---|---|
| 10 | 3 | 放入第 3 格 |
| 17 | 3 | 與 10 碰撞 |
| 24 | 3 | 與 10、17 碰撞 |
| 5 | 5 | 放入第 5 格 |
| 12 | 5 | 與 5 碰撞 |
| 31 | 3 | 與 10、17、24 碰撞 |
以「新節點插入鏈結串列前端」處理碰撞,流程可表示為:
鍵值 k
│
▼
計算 h(k) = k mod 7
│
▼
取出 bucket[h(k)]
│
├── 該位置為空:建立新節點
│
└── 該位置已有資料:將新節點接到鏈結串列前端
最後的雜湊表如下:
索引 鏈結串列
────────────────────────────────
0 NULL
1 NULL
2 NULL
3 [31] -> [24] -> [17] -> [10] -> NULL
4 NULL
5 [12] -> [5] -> NULL
6 NULL
例如查找鍵值 時,先計算:
接著只需在第 3 格的鏈結串列中依序比對鍵值。
雜湊表資料結構
每個節點包含鍵值、資料內容,以及指向下一個節點的指標;雜湊表則包含大小 與存放各串列起點的陣列。
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
typedef struct HashNode {
int key;
int data;
struct HashNode *next;
} HashNode;
typedef struct {
size_t m; // 雜湊表大小
HashNode **bucket; // 每個位置指向一條鏈結串列
} HashTable;
雜湊表初始化:
bool hashInit(HashTable *ht, size_t m)
{
if (m == 0)
return false;
HashNode **array = calloc(m, sizeof(HashNode *));
if (array == NULL)
return false;
ht->m = m;
ht->bucket = array;
return true;
}
雜湊函數:
size_t hashIndex(const HashTable *ht, int key)
{
long long r = (long long)key % (long long)ht->m;
if (r < 0)
r += (long long)ht->m;
return (size_t)r;
}
插入資料時,若鍵值已存在則更新資料;若不存在則建立新節點並接到鏈結串列前端:
第 2 題5 分
ii. (5 points) Design a hash function and a probing function. Use 10 data to show how data are stored to the location calculated by the hash function and how probing function works when a collision occurs.
登入後即可作答並保存紀錄。
核心觀念
雜湊表(hash table)使用雜湊函數將資料鍵值 映射到陣列位置:
若兩個不同鍵值被映射到相同位置,即發生碰撞(collision)。本題採用開放定址法(open addressing),所有資料都直接存放在雜湊表內;發生碰撞時,使用 probing function 依序尋找下一個空位置。
本題設計如下:
- 雜湊表大小:,位置為 至
- 雜湊函數:除法取餘法
- 探測函數:線性探測(linear probing)
其中 時先嘗試雜湊函數算出的原始位置;若該位置已被占用,便令 ,依序探測下一個位置。
由於每次增加 ,並搭配 ,最多會訪問表中的全部 個位置,因此只要表內仍有空位,就能找到儲存位置。
解題方法
資料依下列順序插入:
插入規則為:
- 計算 。
- 先檢查 。
- 若該位置已占用,依序檢查 。
- 找到第一個空位置後,將資料存入該位置。
| 插入資料 | 探測位置順序 | 實際存放位置 | |
|---|---|---|---|
第 2 題10 分
iii. (10 points) Define the hash function and the probing function. Be sure to define the function name, parameter list, return value, local variables, and calling method. Use C or pseudo code to complete your answer.
登入後即可作答並保存紀錄。
核心觀念
題目未指定雜湊表大小與碰撞處理法,以下採用標準假設:鍵值為整數、使用開放定址法,表格容量為正整數 ,碰撞採用線性探查(linear probing)。
雜湊函式將鍵值映射至雜湊表的初始位置:
在 C 語言中,負數取餘數可能得到負值,因此可修正為:
若初始位置已有其他資料,便依序探查下一個位置。線性探查的位移函式為:
其中 為第幾次探查,。實際探查位置為:
因此:
- :檢查雜湊函式產生的初始位置。
- :檢查下一個位置。
- :再檢查下一個位置。
- 到達表尾時,透過取餘數回到表首。
解題方法
先以雜湊函式求出鍵值的初始位置,再以探查函式依序產生實際位置。以 、 為例:
探查順序為:
這個順序會檢查整張表,適合用於雜湊表的插入與搜尋。
C 語言實作
第 2 題10 分
iv. (10 points) Analyze the time complexity of the procedure of the hashing procedure using your functions above and show the result in the Big-O notation. Be sure to show the steps in analysis and the associated detail calculation.
登入後即可作答並保存紀錄。
核心觀念
本小題未附前文所稱的雜湊函式與碰撞處理方式;以下採用標準假設:雜湊表有 個位置,儲存 筆資料,使用分離鏈結法處理碰撞,且固定長度鍵值的雜湊函式計算為 。
負載因子定義為
雜湊法的效率取決於:
- 雜湊函式計算成本;
- 是否發生碰撞;
- 發生碰撞後需要檢查多少筆資料;
- 負載因子 是否維持在常數範圍。
解題方法
一次查找或插入的主要流程可抽象為:
i ← h(key)
p ← table[i]
while p 不為空:
比較 p.key 與 key
p ← p.next
其中:
- 計算 :;
- 透過索引取得
table[i]:; - 掃描碰撞鏈結串列:取決於該串列長度。
設第 個位置的鏈結串列長度為 。在簡單均勻雜湊的假設下,每筆資料落入任一位置的機率為 ,因此
不成功查找平均需檢查 筆資料;成功查找平均需檢查的資料筆數約為
所以一次操作的平均時間為
當雜湊表維持固定負載因子,即 時,
最壞情況分析
最壞情況是所有鍵值都被雜湊到同一個位置:
此時該鏈結串列長度為 ,查找、刪除或需要先檢查重複鍵值的插入操作,都可能逐一比較全部資料,因此
若雜湊函式本身需要逐字掃描長度為 的字串,則應寫成
第 3 題5 分
- Stacks. Consider the following stack storing integers and answer the following questions.
i. (5 points) Define the data structures needed to represent this stack.
登入後即可作答並保存紀錄。
核心觀念
Stack(堆疊)遵守 LIFO(Last In, First Out,後進先出)。要表示一個儲存整數的堆疊,至少需要:
- 儲存整數元素的空間。
- 記錄堆疊頂端位置的變數
top。 - 若使用固定大小陣列,還需要定義最大容量。
題幹文字未附所稱的堆疊圖形,也未指定陣列或鏈結串列;以下採最常見的固定容量陣列表示法。
解題方法
令 top 表示「目前元素個數」,同時也是下一個可插入的位置,則資料結構可定義為:
#define MAX_STACK_SIZE 100
typedef struct {
int data[MAX_STACK_SIZE];
int top;
} Stack;
初始化時:
void initStack(Stack *s) {
s->top = 0;
}
當堆疊中有 個元素時:
- 有效元素為
data[0]到data[n - 1]。 top == n。- 堆疊頂端元素為
data[top - 1]。 - 堆疊底端元素為
data[0]。
因此:
- 空堆疊:
top == 0 - 堆疊已滿:
top == MAX_STACK_SIZE
插入與刪除的關鍵操作為:
/* push */
s->data[s->top] = x;
s->top++;
/* pop */
s->top--;
x = s->data[s->top];
第 3 題30 分
ii. (30 points) Using your data structures, define the functions to NEW, PUSH and POP. The function NEW is to create and initialize a stack. Be sure to define the function name, parameter list, return value, and local variables. Use C or pseudo code to complete your answer. Provide a main function to call these functions in sequence to store and retrieve data.
登入後即可作答並保存紀錄。
核心觀念
本題考查堆疊(Stack)的抽象資料型態與基本操作。堆疊遵守 後進先出(LIFO, Last In First Out):
NEW:建立並初始化空堆疊。PUSH:將資料放入堆疊頂端。POP:移除並取出堆疊頂端資料。- 堆疊為空時執行
POP,稱為 underflow。 - 堆疊容量已滿時執行
PUSH,稱為 overflow。
以下採用「陣列+整數 top」實作,其中 top 表示目前堆疊內的元素數量。因此:
當堆疊非空時,頂端元素位於:
解題方法
建立結構如下:
data:存放堆疊元素的陣列。capacity:堆疊最大容量。top:目前元素個數,空堆疊時為 。
操作流程:
NEW(capacity)配置結構與陣列,並令top = 0。PUSH先確認堆疊未滿,再將資料放入data[top],最後令top加一。POP先確認堆疊非空,令top減一,再取出data[top]。
函式介面與回傳值
| 函式 | 參數 | 回傳值 |
|---|---|---|
NEW(int capacity) | 堆疊容量 | 成功回傳 Stack *,失敗回傳 NULL |
PUSH(Stack *s, int value) | 堆疊指標、待存入資料 | 操作狀態 |
POP(Stack *s, int *value) | 堆疊指標、存放取出資料的位址 | 操作狀態 |
由於 C 函式只能直接回傳一個值,POP 使用 int *value 將取出的資料寫回呼叫端,並以回傳值表示操作是否成功。