108 年 國立成功大學工程科學系碩士班乙組《資料結構》
第 1 題12 分
Complete the functions of inserting a newnode into a doubly linked circular list and deleting one node from a doubly linked circular list.
void dinsert(node_pointer node, node_pointer newnode)
{
(1);
(2);
(3);
(4);
}
void ddelete(node_pointer node, node_pointer deleted)
{
if (node == deleted) printf("Deletion of head node not permitted.\n");
else {
(1);
(2);
free(deleted);
}
}
🖼️【此處有附圖,請對照原卷】
登入後即可作答並保存紀錄。
核心觀念
本題考查「雙向循環鏈結串列」的節點插入與刪除。
每個節點具有兩個指標:
llink:指向前一個節點rlink:指向後一個節點
循環鏈結串列的特性是:
- 最後一個節點的
rlink指向第一個節點 - 第一個節點的
llink指向最後一個節點
假設 dinsert(node, newnode) 表示將 newnode 插入在 node 的後方。
解題方法
插入新節點時,必須同時更新四個指標:
newnode的前方指向nodenewnode的後方指向原本node的後方節點- 原後方節點的前方改指向
newnode node的後方改指向newnode
完整程式碼如下:
void dinsert(node_pointer node, node_pointer newnode)
{
newnode->llink = node;
newnode->rlink = node->rlink;
node->rlink->llink = newnode;
node->rlink = newnode;
}
刪除 deleted 節點時,讓其前方節點與後方節點直接相連:
void ddelete(node_pointer node, node_pointer deleted)
{
if (node == deleted)
printf("Deletion of head node not permitted.\n");
else {
deleted->llink->rlink = deleted->rlink;
deleted->rlink->llink = deleted->llink;
free(deleted);
}
}
其中 node == deleted 表示欲刪除的是表頭節點,依題目規定不允許刪除。
指標關係檢查
插入後,應符合:
第 2 題8 分
Please write down every sequence of each step for finding the Minimum Cost Spanning Tree using Kruskal and Prim Algorithms.
🖼️【此處有附圖,請對照原卷】
登入後即可作答並保存紀錄。
核心觀念
最小成本生成樹(MST)要連接圖中的所有頂點,且不能形成環。含 個頂點的生成樹恰有 條邊;本題圖中頂點為 ,共 個,因此 MST 應有 條邊。
Kruskal 演算法依邊權重由小到大檢查,只有在加入後不形成環時才選取。Prim 演算法從一個起點開始,每一步選擇連接目前樹內與樹外頂點的最小權重邊。兩種方法都以「不形成環、逐步連通所有頂點」來建立 MST。
解題方法
圖中讀得 8 個頂點及 10 條加權邊:
、、、、、、、、、。
Kruskal 演算法
將邊依權重由小到大排列;權重相同的邊可任意排列。
| 次序 | 邊 | 權重 | 判斷 |
|---|---|---|---|
| 1 | 3 | 選取 | |
| 2 | 5 | 選取 | |
| 3 | 6 | 選取 | |
| 4 | 7 | 選取 | |
| 5 | 8 | 選取 | |
| 6 | 8 | 選取 | |
| 7 | 9 | 選取,加入後共有 條邊 | |
| 8 | 10 | 略過,兩端已連通,加入會形成環 | |
| 9 | 12 | 略過,兩端已連通,加入會形成環 | |
| 10 | 14 | 略過,兩端已連通,加入會形成環 |
選取順序為
其中權重同為 的 與 ,先後次序可以交換。
第 3 題80 分
For the sequence: 33, 32, 23, 7, 5, 100, 46, 67, 20. Please write down every sequence of each step while applying Insertion Sort, Quick Sort, Interactive Merge Sort, Recursive Merge Sort, MAX Heap Sort and LSD Radix Sort (5%), then complete the Sort algorithms parts indicated by XXX (2%) (80%)
#define MAX-SIZE 1000/* maximum size of list plus one */
typedef struct {
int key;
/* other fields */
} element;
element list[MAX-SIZE];
void adjust(element list[], int root, int n)
{
int child, rootkey;
element temp;
temp=list[root];
rootkey=list[root].key;
child=2*root;
while (child <= n) {
if ((child < n) && (list[child].key < list[child+1].key))
child++;
if (rootkey > list[child].key) break;
else {
XXXX_1;
XXXX_2;
}
}
list[child/2] = temp;
}
void heapsort(element list[], int n)
{
int i, j;
element temp;
for (i=n/2; i>0; i--)
XXXX_3;
for (i=n-1; i>0; i--) {
SWAP(list[1], list[i+1], temp);
XXXX_4;
}
}
void quicksort(element list[], int left, int right)
{
int pivot, i, j;
element temp;
if (left < right) {
i = left; j = right+1;
pivot = list[left].key;
do {
do i++; while (XXXX_5);
do j--; while (XXXX_6);
if (i < j) SWAP(list[i], list[j], temp);
} while (i < j);
SWAP(list[left], list[j], temp);
quicksort(XXXX_7);
quicksort(XXXX_8);
}
}
void merge(element list[], element sorted[], int i, int m, int n)
{
int j, k, t;
j = m+1;
k = i;
while (i<=m && j<=n) {
if (list[i].key<=list[j].key)
XXXX_9;
else XXXX_10;
}
if (i>m) for (XXXX_11)
sorted[k+t-j]= list[t];
else for (XXXX_12)
sorted[k+t-i] = list[t];
}
void merge_pass(element list[], element sorted[], int n, int length)
{
int i, j;
for (i=0; i<n-2*length; i+=2*length)
merge(XXXX_13);
if (i+length<n)
merge(XXXX_14);
else
for (j=i; j<n; j++) sorted[j]= list[j];
}
void merge_sort(element list[], int n)
{
int length=1;
element extra[MAX-SIZE];
while (length<n) {
merge_pass(XXXX_15);
length *= 2;
merge_pass(XXXX_16);
length *= 2;
}
}
int rmerge(element list[], int lower, int upper)
{
int middle;
if (lower >= upper) return lower;
else {
middle = XXXX_17;
return listmerge(XXXX_18);
}
}
int listmerge(element list[], int first, int second)
{
int start=n;
while (first!=-1 && second!=-1) {
if (list[first].key<=list[second].key) {
/* key in first list is lower, link this
element to start and change start to
point to first */
XXXX_19;
start = first;
XXXX_20;
} else {
/* key in second list is lower, link this
element into the partially sorted list */
XXXX_21;
start = second;
XXXX_22;
}
}
if (first-1)
list[start].link = second;
else
list[start].link = first;
return list[n].link;
}
#define MAX_DIGIT 3
#define RADIX_SIZE 10
typedef struct list_node *list_pointer;
typedef struct list_node {
int key[MAX_DIGIT];
list_pointer link;
} list_node;
list_pointer radix_sort(list_pointer ptr)
{
list_pointer front[RADIX_SIZE], rear[RADIX_SIZE];
int i, j, digit;
for (i=MAX_DIGIT-1; i>=0; i--) {
for (j=0; j<RADIX_SIZE; j++)
front[j]=rear[j]=NULL;
while (ptr) {
digit=ptr->key[i];
if (!front[digit]) XXXX_23;
else rear[digit]->link=ptr;
XXXX_24;
ptr=ptr->link;
}
/* reestablish the linked list for the next pass */
ptr= NULL;
for (j=RADIX_SIZE-1; j>=0; j--)
if (front[j]) {
XXXX_25;
ptr=front[j];
}
}
return ptr;
}
登入後即可作答並保存紀錄。
核心觀念
本題綜合考查六種排序法,以及堆積排序、快速排序、合併排序與 LSD 基數排序的程式填空。
排序方向以「由小到大」為一般標準;但題目給出的 radix_sort 在重建鏈結串列時,從 bucket 9 往 0 串接,因此實際結果是「由大到小」。
一、Insertion Sort
Insertion Sort 的想法是:左側視為已排序區,每次取右側第一個元素,插入左側適當位置。
原始序列:
每次插入後:
-
插入
32, 33, 23, 7, 5, 100, 46, 67, 20 -
插入
23, 32, 33, 7, 5, 100, 46, 67, 20 -
插入
7, 23, 32, 33, 5, 100, 46, 67, 20 -
插入
5, 7, 23, 32, 33, 100, 46, 67, 20 -
插入
5, 7, 23, 32, 33, 100, 46, 67, 20 -
插入
5, 7, 23, 32, 33, 46, 100, 67, 20 -
插入
5, 7, 23, 32, 33, 46, 67, 100, 20 -
插入
5, 7, 20, 23, 32, 33, 46, 67, 100
時間複雜度:平均與最壞為 ,最佳為 ;額外空間為 。
二、Quick Sort
程式採用:
- pivot:區間最左側元素
i從左往右找第一個 的元素j從右往左找第一個 的元素- 若 ,交換兩者
- 最後將 pivot 與
list[j]交換
因此填空為:
XXXX_5:list[i].key < pivot
XXXX_6:list[j].key > pivot
第一次 partition
pivot :
原始:
33, 32, 23, 7, 5, 100, 46, 67, 20
交換 與 :
33, 32, 23, 7, 5, 20, 46, 67, 100
pivot 與 交換:
20, 32, 23, 7, 5, 33, 46, 67, 100
分成:
- 左區間:
20, 32, 23, 7, 5 - 右區間:
46, 67, 100
排序左區間
pivot :
20, 32, 23, 7, 5
交換 與 :
20, 5, 23, 7, 32
交換 與 :
20, 5, 7, 23, 32
pivot 與 交換:
7, 5, 20, 23, 32
接著排序 7, 5:
5, 7, 20, 23, 32
排序右區間
右區間為:
46, 67, 100
pivot ,已符合分割結果:
46, 67, 100
再以 為 pivot:
46, 67, 100
Quick Sort 各階段結果
33, 32, 23, 7, 5, 100, 46, 67, 20
20, 32, 23, 7, 5, 33, 46, 67, 100
7, 5, 20, 23, 32, 33, 46, 67, 100
5, 7, 20, 23, 32, 33, 46, 67, 100
填空:
XXXX_7:left, j-1
XXXX_8:j+1, right
完整遞迴呼叫為:
quicksort(list, left, j-1);
quicksort(list, j+1, right);
平均時間複雜度為 ,最壞為 ;遞迴堆疊平均為 。
三、Interactive Merge Sort
Iterative Merge Sort 每一輪將相鄰的已排序區段合併,區段長度依序為:
長度為 的區段合併
32, 33, 7, 23, 5, 100, 46, 67, 20
長度為 的區段合併
7, 23, 32, 33, 5, 46, 67, 100, 20
長度為 的區段合併
5, 7, 23, 32, 33, 46, 67, 100, 20
長度為 的區段合併
5, 7, 20, 23, 32, 33, 46, 67, 100
merge 的參數是:
merge(list, sorted, 起點, 左半結束位置, 右半結束位置);
填空:
XXXX_9:sorted[k++] = list[i++]
XXXX_10:sorted[k++] = list[j++]
剩餘元素拷貝:
XXXX_11:t=j; t<=n; t++
XXXX_12:t=i; t<=m; t++
兩個 merge_pass:
XXXX_13:list, sorted, i, i+length-1, i+2*length-1
XXXX_14:list, sorted, i, i+length-1, n-1
merge_sort 中:
XXXX_15:list, extra, n, length
XXXX_16:extra, list, n, length
完整核心程式:
while (length < n) {
merge_pass(list, extra, n, length);
length *= 2;
merge_pass(extra, list, n, length);
length *= 2;
}
時間複雜度為 ,額外空間為 。
四、Recursive Merge Sort
Recursive Merge Sort 先不斷切割,再由下而上合併。
第一次合併相鄰單一元素:
32, 33, 23, 7, 5, 100, 46, 67, 20
32, 33, 7, 23, 5, 100, 46, 67, 20
合併前四個元素:
7, 23, 32, 33, 5, 100, 46, 67, 20
右半部先合併:
7, 23, 32, 33, 5, 100, 46, 67, 20
7, 23, 32, 33, 5, 46, 67, 100, 20
最後合併:
5, 7, 23, 32, 33, 46, 67, 100, 20
5, 7, 20, 23, 32, 33, 46, 67, 100
填空:
第 4 題
Complete the following sorting functions by filling in the blanks.
Recursive Merge Sort
merge_pass(____);
length *= 2;
merge_pass(____);
length *= 2;
}
}
int rmerge(element list[], int lower, int upper)
{
int middle;
if (lower >= upper)
return lower;
else {
middle = ____;
return listmerge(____);
}
}
int listmerge(element list[], int first, int second)
{
int start = n;
while (first != -1 && second != -1) {
if (list[first].key <= list[second].key) {
/* key in first list is lower, link this
element to start and change start to
point to first */
____;
start = first;
____;
}
else {
/* key in second list is lower, link this
element into the partially sorted list */
____;
start = second;
____;
}
}
if (first == -1)
list[start].link = second;
else
list[start].link = first;
return list[n].link;
}
LSD Radix Sort
#define MAX_DIGIT 3
#define RADIX_SIZE 10
typedef struct list_node *list_pointer;
typedef struct list_node {
int key[MAX_DIGIT];
list_pointer link;
}
list_pointer radix_sort(list_pointer ptr)
{
list_pointer front[RADIX_SIZE],
rear[RADIX_SIZE];
int i, j, digit;
for (i = MAX_DIGIT - 1; i >= 0; i--) {
for (j = 0; j < RADIX_SIZE; j++)
front[j] = rear[j] = NULL;
while (ptr) {
digit = ptr->key[i];
if (!front[digit])
____;
else
rear[digit]->link = ptr;
____;
ptr = ptr->link;
}
/* reestablish the linked list for the next pass */
ptr = NULL;
for (j = RADIX_SIZE - 1; j >= 0; j--)
if (front[j]) {
____;
ptr = front[j];
}
}
return ptr;
}
登入後即可作答並保存紀錄。
核心觀念
本題考兩種排序法,以及如何用鏈結欄位 link 操作串列:
- 合併排序(Merge Sort):將已排序的子串列合併。每次合併兩個長度約為
length的子串列後,下一輪的子串列長度加倍。 - 遞迴合併排序:把索引範圍切成左右兩半,遞迴排序後,再合併兩個排序好的鏈結串列。
- LSD 基數排序:從個位數開始,依序按低位數到高位數分桶;每一輪都須維持同一桶內元素的原相對次序,才能確保排序正確。
解題方法
1. 兩趟交替的合併排序
每次 merge_pass 把來源陣列中的相鄰子串列合併到目的陣列。兩個陣列交替當來源與目的地,避免每一輪都把結果複製回原陣列。
merge_pass(list, extra, n, length);
length *= 2;
merge_pass(extra, list, n, length);
length *= 2;
第一趟將 list 合併到 extra;子串列長度加倍後,第二趟再將 extra 合併回 list。每趟完成後,已排序子串列的長度加倍。
2. 遞迴合併排序與串列合併
遞迴分割的中點為 。左右兩半各自排序後,將兩個排序串列的起點交給 listmerge。
int rmerge(element list[], int lower, int upper)
{
int middle;
if (lower >= upper)
return lower;
else {
middle = (lower + upper) / 2;
return listmerge(
list,
rmerge(list, lower, middle),
rmerge(list, middle + 1, upper)
);
}
}
listmerge 使用 n 作為虛擬起點:每次比較兩串列目前節點的鍵值,將較小節點接到已排序串列尾端,再移動該串列的指標。鍵值相等時選 first,因此合併具有穩定性。
int listmerge(element list[], int first, int second)
{
int start = n;
while (first != -1 && second != -1) {
if (list[first].key <= list[second].key) {
list[start].link = first;
start = first;
first = list[first].link;
}
else {
list[start].link = second;
start = second;
second = list[second].link;
}
}
if (first == -1)
list[start].link = second;
else
list[start].link = first;
return list[n].link;
}
其中 -1 表示串列結尾。迴圈結束時,最多只剩一串列尚未接完,因此可直接把剩餘串列接到 start 後方。list[n].link 是合併結果的起點。