contributed by <I-Ying-Tsai
>
i-ying-tsai@i-ying-tsai-G5-KF5:~/linux2025/lab0-c$ uname -a
Linux i-ying-tsai-G5-KF5 6.11.0-17-generic #17~24.04.2-Ubuntu SMP PREEMPT_DYNAMIC Mon Jan 20 22:48:29 UTC 2 x86_64 x86_64 x86_64 GNU/Linux
i-ying-tsai@i-ying-tsai-G5-KF5:~/linux2025/lab0-c$ gcc --version
gcc (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0
Copyright (C) 2023 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
i-ying-tsai@i-ying-tsai-G5-KF5:~/linux2025/lab0-c$ ldd --version
ldd (Ubuntu GLIBC 2.39-0ubuntu8.4) 2.39
Copyright (C) 2024 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Written by Roland McGrath and Ulrich Drepper.
i-ying-tsai@i-ying-tsai-G5-KF5:~/linux2025/lab0-c$ lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Address sizes: 39 bits physical, 48 bits virtual
Byte Order: Little Endian
CPU(s): 16
On-line CPU(s) list: 0-15
Vendor ID: GenuineIntel
Model name: 13th Gen Intel(R) Core(TM) i7-13620H
CPU family: 6
Model: 186
Thread(s) per core: 2
Core(s) per socket: 10
Socket(s): 1
Stepping: 2
CPU(s) scaling MHz: 33%
CPU max MHz: 4900.0000
CPU min MHz: 400.0000
BogoMIPS: 5836.80
目的
建立並初始化一個 Linux Kernel
風格的雙向鏈結串列(doubly linked list)頭節點。
head
大小的記憶體空間(透過 malloc
)。NULL
。INIT_LIST_HEAD
初始化該節點,使其的 prev
和 next
都指向自己。head
地址。目的
釋放整個鏈結串列佔用的記憶體,避免記憶體洩漏 (memory leak)。
head
為 NULL
,則直接返回,避免無效操作。list_for_each_safe
進行走訪,確保在刪除節點時不影響遍歷順序。
list_for_each
,則刪除當前節點後,next
指標將指向已被釋放的記憶體,可能導致 迷途指標 (dangling pointer) 或 segmentation fault。list_for_each_safe
會預先存儲 next
節點,確保刪除當前節點後,能繼續正確遍歷。list_entry
取得 element_t
結構,確保可以正確存取 value
。list_del
將節點從鏈結串列中移除。element_t->value
,確保字串記憶體被回收。element_t
本身,確保節點記憶體被回收。head
本身,確保鏈結串列頭部的記憶體也被回收。目的
在鏈結串列的頭部插入一個新的元素。
head
和 s
是否為 NULL
,確保鏈結串列有效。element_t
節點的記憶體,確保成功分配。strdup
為 value
分配記憶體,確保成功存儲字串。list_add
將該節點插入 head
之後,確保新節點成為新的頭部true
表示成功,或 false
表示失敗。目的
在鏈結串列的尾端插入一個新的元素。
head
和 s
是否為 NULL
,確保鏈結串列有效,避免無效操作。element_t
節點的記憶體 (malloc
),確保成功分配,避免 NULL
指標操作。strdup
為 value
分配記憶體,確保字串內容獨立,不受外部變更影響。
strdup
失敗,則釋放 element_t
節點,避免記憶體洩漏。list_add_tail
插入節點至 head
之前,確保新節點成為尾部節點。true
表示成功,或 false
表示失敗。目的
從鏈結串列的頭部刪除一個節點,並返回該節點的指標。
head
是否為 NULL
或 list_empty(head)
,確保鏈結串列有效,避免無效操作。head
之後的第一個節點 (head->next
),確保該節點可移除。list_del
從鏈結串列中移除該節點,確保鏈結完整且不影響其他節點。sp
非空,則複製 value
到 sp
,並確保字串長度不超過 bufsize
,防止 buffer overflow
。element_t *
),由呼叫者負責釋放記憶體 (free(node)
)。比較:
list_del
直接移除節點,確保 O(1)
時間複雜度。目的
從鏈結串列的尾部刪除一個節點,並返回該節點的指標。
head
是否為 NULL
或 list_empty(head)
,確保鏈結串列有效,避免無效操作。head
之前的最後一個節點 (head->prev
),確保該節點可移除。list_del
將該節點從鏈結串列中移除,確保鏈結完整且不影響其他節點。sp
非空,則複製 value
到 sp
,並確保字串長度不超過 bufsize
,防止 buffer overflow
。element_t *
),由呼叫者負責釋放記憶體。比較:
list_del
直接移除尾部節點,確保 O(1) 時間複雜度,不影響其他節點。目的
刪除鏈結串列的中間節點。
false
。slow
& fast
) 找到中間節點。
q_size
計算長度,再走訪到 n/2
位置刪除,則時間複雜度為 O(2n) = O(n)。list_del
將該節點從鏈結串列中移除。value
及其記憶體,確保無記憶體洩漏。true
表示刪除成功。目的
刪除鏈結串列中所有重複的節點,只保留唯一值的節點。
head
是否為 NULL
或 list_empty(head)
,確保鏈結串列有效,避免無效操作。value
。list_del
移除節點,並 free
該節點的 value
及其記憶體,防止記憶體洩漏。true
表示至少刪除了一個重複節點,否則返回 false
。目的
交換鏈結串列中每兩個相鄰的節點。
list_move
進行交換,確保鏈結關係正確。
value
,會降低時間複雜度,但不符合 Linux 內核的鏈結串列設計,因為節點通常存儲指標而非具體值。目的
反轉鏈結串列的順序,使原本的頭節點變為尾節點,尾節點變為頭節點。
next
和 prev
指標。list_move
,可以逐個將節點移動到 head
之前,達到反轉效果,但時間複雜度較高。next
和 prev
指標,時間複雜度為 O(n)。目的
將鏈結串列的節點按照 k
個為一組進行反轉。
k <= 1
或鏈結串列長度小於 k
,則無需反轉。k
個節點。list_for_each_safe
遍歷鏈結串列,每 k
個節點作為一組進行反轉。q_reverse
逐個處理 k 個節點,則需要額外的子串列管理邏輯,但可以利用 list_move_tail
簡化操作。目的
對鏈結串列內的元素進行排序(遞增或遞減),提升查詢效率。
head
為 NULL
或為空,則直接返回。merge_sort
遞迴拆分鏈結串列,使用快慢指標 (slow & fast
) 找到中間節點。merge
涵式合併排序好的子鏈結串列,並根據 descend
參數決定排序順序。head
,確保首尾節點連結正確。目的
移除鏈結串列中 左側值大於右側某個值 的所有節點,使得最終結果呈現遞增順序。
head
為 NULL
或為空,則直接返回 0
。min_elem
變數追蹤當前最小值,若當前節點值大於 min_elem
,則刪除該節點。list_del
移除節點,並釋放該節點的 value
及其記憶體。head->prev
以確保鏈結串列結構完整,最後返回剩餘節點數。目的
移除鏈結串列中 左側值小於右側某個值 的所有節點,使得最終結果呈現遞減順序。
head
為 NULL
或為空,則直接返回 0
。max_elem
變數追蹤當前最大值,若當前節點值小於 max_elem
,則刪除該節點。list_del
移除節點,並釋放該節點的 value
及其記憶體。目的
將多個鏈結串列合併為一個有序鏈結串列(遞增或遞減)。
head
為 NULL
或為空,則返回 0
。queue_contex_t
作為主佇列 (main_ctx
),確保合併後仍有主鏈結串列存在。ctx->q
),將所有節點移動至 main_ctx->q
的尾部。q_sort
重新對合併後的鏈結串列進行排序,確保最終結果有序。size
記錄總元素數量。比較:
在 qtest
中新增了一個名為 shuffle
的命令,其功能是隨機排列 queue
中的元素。為了確保公平性,我們使用 Fisher-Yates Shuffle 演算法,使每個排列的機率相等。本報告將詳細描述 q_shuffle
的實作方式、測試方法以及測試結果。
q_shuffle
的實作Fisher-Yates Shuffle 是一種常用於 均勻隨機打亂 陣列的演算法,其原理如下:
j
(0 ≤ j ≤ i)。arr[i]
與 arr[j]
的位置。我的 q_shuffle
函數實作如下:
void q_shuffle(struct list_head *head)
{
if (!head || list_empty(head) || head->next == head->prev) {
return;
}
int len = q_size(head);
element_t **arr = malloc(len * sizeof(element_t *));
if (!arr)
return;
struct list_head *cur = head->next;
for (int i = 0; i < len; i++, cur = cur->next) {
arr[i] = list_entry(cur, element_t, list);
}
srand(time(NULL));
for (int i = len - 1; i > 0; i--) {
int j = rand() % (i + 1);
if (i != j) {
list_move(&arr[i]->list, &arr[j]->list);
}
}
q_show(3);
free(arr);
}
shuffle
命令的新增在 qtest.c
中,我新增了 do_shuffle
來呼叫 q_shuffle
,並且報告執行結果。
static bool do_shuffle(int argc, char *argv[])
{
if (!current || !current->q) {
report(1, "No queue created");
return false;
}
q_shuffle(current->q);
return true;
}
並加上 ADD_COMMAND(shuffle, "Shuffle elements in queue", "");
來讓 qtest
支援 shuffle
命令。
由測試用的 Python 腳本 test_shuffle.py 測試,得出以下結果以及作圖。
i-ying-tsai@i-ying-tsai-G5-KF5:~/linux2025/lab0-c$ python3 test_shuffle.py
Expectation: 41666
Observation: {'1234': 41807, '1243': 41685, '1324': 41933, '1342': 41378, '1423': 41784, '1432': 41697, '2134': 41739, '2143': 41481, '2314': 41766, '2341': 42083, '2413': 41585, '2431': 41735, '3124': 41610, '3142': 41633, '3214': 41857, '3241': 41499, '3412': 41837, '3421': 41343, '4123': 41784, '4132': 41813, '4213': 41640, '4231': 41628, '4312': 41456, '4321': 41227}
Chi-Square sum: 21.61868189891038
在這個測試中,虛無假設 H₀
是:
q_shuffle() 產生的所有排列是均勻分布的,且各種排列的機率相等。
查表發現,在自由度為 23 的情況下,χ² = 21.6 對應的 p 值大於 0.10。