contributed by <As7r1d
>
$ gcc --version
gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
Copyright (C) 2021 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.
$ 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): 8
On-line CPU(s) list: 0-7
Vendor ID: GenuineIntel
Model name: Intel(R) Core(TM) i5-8250U CPU @ 1.60GHz
CPU family: 6
Model: 142
Thread(s) per core: 2
Core(s) per socket: 4
Socket(s): 1
Stepping: 10
CPU max MHz: 3400.0000
CPU min MHz: 400.0000
BogoMIPS: 3600.00
INIT_LIST_HEAD
達成該函式要求,並且使用 malloc
配置記憶體區塊給 head。./qtest
cmd> new
l = []
cmd> ih a
l = [a]
cmd> ih b
l = [b a]
cmd> ih c
l = [c b a]
cmd> free
l = NULL
element_t
,element_t
表示鏈結串列中之元素。element_t
所佔據的空間。./qtest
cmd> new
l = []
cmd> ih a
l = [a]
cmd> ih b
l = [b a]
cmd> free
l = NULL
strdup()
為一個字串分配新的記憶體,並把原始字串的內容複製過去。這樣就不會跟原始字串共用同一塊記憶體。malloc(sizeof(element_t))
動態配置記憶體空間給新節點。strdup(s)
為 value 配置記憶體並存入字串內容,複製字串 s 並存入 value。new_element->value = strdup(s);
if (!new_element->value) {
free(new_element);
return false;
}
list_add(&new_element->list, head);
list_add(&new_element->list, head)
把新節點加到佇列開頭。list_add_tail(&new_element->list, head)
把新節點加到佇列尾端。cmd> new
l = []
cmd> ih a
l = [a]
cmd> ih b
l = [b a]
cmd> it c
l = [b a c]
cmd> it d
l = [b a c d]
功能:
實作內容:
list_entry(node, type, member)
從 struct list_head *
回推到 type *(element_t *)
。 if (!head || list_empty(head)) {
return NULL;
}
struct list_head *first = head->next;
element_t *element = list_entry(first, element_t, list);
sp
不是 NULL,就複製字串內容sp
,但最多只會複製 bufsize - 1
個字元\0
,確保字串結束。 if (sp != NULL && bufsize > 0) {
strncpy(sp, element->value, bufsize - 1);
sp[bufsize - 1] = '\0';
}
程式碼: Commit 5eb769f
qtest:
l = [c b a]
cmd> rh
Removed c from queue
l = [b a]
cmd> rt
Removed a from queue
l = [b]
cmd> ih a
l = [a]
cmd> ih b
l = [b a]
cmd> ih c
l = [c b a]
cmd> size
Queue size = 3
l = [c b a]
while (fast->next != head && fast->next->next != head) {
slow = slow->next;
fast = fast->next->next;
}
element_t *element = list_entry(slow, element_t, list);
list_del(slow);
cmd> size
Queue size = 3
l = [c b a]
cmd> dm
l = [c a]
current
代表目前正在檢查的節點。從佇列的第一個有效節點開始,不包含 head。一一比對 current 和後面的所有節點,找出重複的字串。
struct list_head *current = head->next;
strcmp()
比對兩個字串,如果相等,表示找到重複的節點
if (strcmp(current_element->value, compare_element->value) == 0)
if (found_duplicate) {
list_del(current);
}
l = [c b c c a]
cmd> dedup
ERROR: Duplicate strings are in queue or distinct strings are not in queue
l = [b a]
由於會出現上述error,目前還在修正。
struct list_head *first = current;
struct list_head *second = current->next;
list_del(second);
list_add(second, first->prev);
current = first->next;
l = [h g f e d c b a]
cmd> swap
l = [g h e f c d a b]
cmd>
功能: 以反向順序重新排列鏈結串列,該函式不該配置或釋放任何鏈結串列節點。
實作內容:
while (current != head) {
temp = current->next;
current->next = current->prev;
current->prev = temp;
current = temp;
}
temp = current->next
:暫存 current->next,確保反轉後能找到下一個節點。
current->next = current->prev
:讓 next 指向原本的 prev。
current->prev = temp
:讓 prev 指向原本的 next
程式碼:Commit a745816
qtest:
l = [g h e f c d a b]
cmd> reverse
l = [b a d c f e h g]
list_cut_position()
這個函式,它能夠直接將前 k 個節點切割出來形成一個新的鏈結。當 k 個節點被切割出來後,接下來反轉。這裡直接利用了 q_reverse(),因為它已經能夠有效地反轉整個雙向鏈結串列,所以不需要再手寫反轉邏輯,這樣可以讓程式碼更模組化,提升可讀性。list_splice_tail()
來將反轉後的區塊加到一個新的 result 鏈結。l = [j i b a d c f e h g]
cmd> reverseK 2
l = [i j a b c d e f g h]
功能: 以遞增順序來排序鏈結串列的節點。
實作內容:
list_cut_position(&left, head, slow)
將 head 後半段的節點分割成 left,這樣 head 和 left 各自代表原來的前半段和後半段。struct list_head left;
struct list_head *fast, *slow;
INIT_LIST_HEAD(&left);
slow = head->next;
fast = head->next->next;
while (fast != head && fast->next != head) {
slow = slow->next;
fast = fast->next->next;
}
list_cut_position(&left, head, slow);
q_sort(&left, descend);
q_sort(head, descend);
list_del(chosen)
從原來的鏈結中移除這個節點,list_add_tail(chosen, &result)
將選出的節點加入 result。重複這個過程,直到 left 或 head 其中一個先變空。程式碼:Commit 4d3fa7e
執行 make test 時發現排序是unstable:
# Test of insert_head and remove_head
# Test of insert_head, insert_tail, remove_head, remove_tail, and delete_mid
# Test of insert_head, insert_tail, remove_head, reverse and merge
# Test of insert_head, insert_tail, size, swap, and sort
ERROR: Not stable sort. The duplicate strings "bear" are not in the same order.
# Test of insert_head, insert_tail, remove_head, reverse, size, swap, and sort
ERROR: Not stable sort. The duplicate strings "bear" are not in the same order.
# Test of insert_head, insert_tail, delete duplicate, sort, descend and reverseK
ERROR: Not stable sort. The duplicate strings "gerbil" are not in the same order.
# Test of truncated strings
# Test operations on empty queue
# Test remove_head with NULL argument
# Test operations on NULL queue
# Test of malloc failure on insert_head
# Test of malloc failure on insert_tail
# Test of malloc failure on new
# Test performance of insert_tail, reverse, and sort
Warning: Skip checking the stability of the sort because the number of elements 2000000 is too large, exceeds the limit 100000.
# Test performance of sort with random and descending orders
# 10000: all correct sorting algorithms are expected pass
# 50000: sorting algorithms with O(n^2) time complexity are expected failed
# 100000: sorting algorithms with O(nlogn) time complexity are expected pass
ERROR: Not stable sort. The duplicate strings "vujqm" are not in the same order.
ERROR: Not stable sort. The duplicate strings "vujqm" are not in the same order.
ERROR: Not stable sort. The duplicate strings "iwkui" are not in the same order.
ERROR: Not stable sort. The duplicate strings "iwkui" are not in the same order.
ERROR: Not stable sort. The duplicate strings "cqson" are not in the same order.
Error limit exceeded. Stopping command execution
# Test performance of insert_tail
# Test if time complexity of q_insert_tail, q_insert_head, q_remove_tail, and q_remove_head is constant
Testing insert_tail...(0/10)
因此修改如下,使排序成為stable:
- if ((descend && cmp > 0) || (!descend && cmp < 0))
+ if ((descend && cmp >= 0) || (!descend && cmp <= 0))
l = [i j a b c d e f g h]
cmd> sort
l = [a b c d e f g h i j]
功能:
實作內容:
這兩個函式的目標類似,一開始的思路是左到右遍歷並檢查右側的節點,但當節點過多時對於較大的鏈結串列會很慢。因此參考 LeetCode 2487. Remove Nodes From Linked List 。直接從左到右遍歷並檢查右側的節點,如果我們先反轉鏈結串列,從右側開始往左掃描,那麼右側的節點已經是處理過的,這樣我們只需要單向遍歷一次,時間複雜度變成 O(n)。
反轉鏈結串列:
q_reverse(head);
這樣我們從左到右遍歷時,其實是從原本的最右側往左掃描。
遍歷 cur,確保 cur 是目前掃描到的最大(最小)值。next_node 負責檢查 cur 之後的節點。針對 q_ascend,如果 next_node 有比 cur 小 的值,就刪除 next_node;針對 q_descend,如果 next_node 有比 cur 大的值,就刪除 next_node。最後透過 list_del() 安全移除 next_node,並釋放記憶體。
struct list_head *cur = head->next;
while (cur != head) {
struct list_head *next_node = cur->next;
while (next_node != head) {
element_t *cur_element = list_entry(cur, element_t, list);
element_t *next_element = list_entry(next_node, element_t, list);
struct list_head *next_next = next_node->next;
if (strcmp(cur_element->value, next_element->value) < 0) {
list_del(&next_element->list);
free(next_element->value);
free(next_element);
}
next_node = next_next;
}
cur = cur->next;
}
l = [b a d c f e h g j i]
cmd> ascend
l = [a c e g i]
l = [h d b a c e g i]
cmd> descend
l = [i]
**功能:**合併所有已排序的佇列,並合而為一個新的已排序佇列
實作內容:
當在構思 q_merge 這個函式時,要將多個已排序的鏈結串列合併為一個,並且根據 descend 參數來選擇升序或降序排序。不需要頻繁地比較和插入,而是透過 list_splice_tail() 一次性合併所有鏈結串列,再進行一次排序。
取出 head 內的第一個 queue_contex_t:
queue_contex_t *first_ctx = list_entry(head->next, queue_contex_t, chain);
struct list_head *result_queue = first_ctx->q;
queue_contex_t 是一個封裝鏈結串列的結構,其中 q 存放具體的鏈結串列。first_ctx->q 會作為合併後的最終結果。
遍歷 head 內的所有 queue_contex_t,將所有鏈結串列合併:
struct list_head *chain_node = head->next->next;
while (chain_node != head) {
queue_contex_t *current_ctx = list_entry(chain_node, queue_contex_t, chain);
struct list_head *current_queue = current_ctx->q;
if (!list_empty(current_queue)) {
list_splice_tail(current_queue, result_queue);
first_ctx->size += current_ctx->size;
INIT_LIST_HEAD(current_queue);
current_ctx->size = 0;
}
chain_node = chain_node->next;
}
遍歷 head 內的所有 queue_contex_t,取得 current_queue(當前佇列)。如果 current_queue 不是空的,將 current_queue 透過 list_splice_tail() 併入 result_queue,然後更新 first_ctx->size,因為 result_queue 現在包含了更多節點。清空 current_queue,避免重複計算。繼續遍歷 head 內的下一個 queue_contex_t。
最後對 result_queue 進行排序
q_sort(result_queue, descend);
最終合併後的大小
return first_ctx->size;
程式碼:Commit 4d3fa7e
qtest:
l = [f d c b a]
cmd> new
l = []
cmd> ih g
l = [g]
cmd> ih h
l = [h g]
cmd> ih a
l = [a h g]
cmd> ih b
l = [b a h g]
cmd> merge
l = [a a b b c d f g h]