contributed by <TonyLinX
>
作業書寫規範:
[TOC]
: 筆記左上方已有 Table of Contents (TOC) 功能,不需要畫蛇添足c=
或 cpp=
變更為 c
或 cpp
。行號只在後續討論明確需要行號時,才要出現,否則維持精簡的展現。可留意「你所不知道的 C 語言: linked list 和非連續記憶體」裡頭程式碼展現的方式diff
標示),可附上對應 GitHub commit 的超連結,列出程式碼是為了「檢討」和「便於他人參與討論」:::info
, :::success
, :::warning
等標示,儘量用清晰的文字書寫。:::danger
則僅限授課教師作為批注使用〈
和 〉
,非「小於」和「大於」符號$ uname -a
Linux sag-System-Product-Name 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
$ 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.
$ 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.
$ 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) i7-7700K CPU @ 4.20GHz
CPU family: 6
Model: 158
Thread(s) per core: 2
Core(s) per socket: 4
Socket(s): 1
Stepping: 9
CPU(s) scaling MHz: 100%
CPU max MHz: 4500.0000
CPU min MHz: 800.0000
BogoMIPS: 8400.00
Commit: c0543ae
queue
。INIT_LIST_HEAD
來完成創建一個空的 queue
,使程式碼更加簡潔。malloc
來配置 struct list_head
的空間。INIT_LIST_HEAD
將 next
和 prev
指向自己,使其成為一個獨立的空佇列。malloc
失敗,應回傳 NULL
。Commit: 79759aa
q_new()
中的 *new
參數名字改成 *head
。- struct list_head *new = malloc(sizeof(struct list_head));
- if (new == NULL)
+ struct list_head *head = malloc(sizeof(struct list_head));
+ if (!head)
return NULL;
- INIT_LIST_HEAD(new);
-
- return new;
+ INIT_LIST_HEAD(head);
+ return head;
Commit: a48f43d
q_free
以釋放佇列記憶體。list_for_each_safe
遍歷整個 queue
,並透過 list_entry
計算該 list_head
的 element
的位址就可以釋放指定的記憶體位址,使程式碼更加簡潔。head
是否為 NULL
,如果 head
為 NULL
,則直接返回,不執行任何操作。list_for_each_safe
安全遍歷 queue
中所有 element_t
節點,確保刪除時不影響迭代過程。list_entry
計算出該element_t
節點的記憶體位址。element_t
本身之前,先釋放其 value
,避免記憶體洩漏。element_t
節點,確保所有 queue
節點被適當釋放。head
所指向的 list_head
,確保 queue
本身的記憶體也被釋放。Commit: 3c5ce1b
q_insert_head
以支援頭部插入元素。O(1)
複雜度,同時維持 queue 的一致性。head
以及 s
是否為 NULL
,若其一為 NULL
,則回傳 false
,不執行任何操作。new_element_t
節點的記憶體,確保新節點可以存儲字串。s
到 new_element_t
的 value
,確保字串內容獨立存儲。new_element_t
,避免記憶體洩漏,並回傳 false
。list_add
將新節點插入 head
之後,維持 queue
的雙向鏈結串列結構。true
,確保操作結果可用於錯誤處理。Commit: 81d5ec5
q_insert_tail
以支援尾部插入元素q_insert_head
一樣,只是插入的位置變成 queue
的 tail
。head
是否為 NULL
,如果 head
為 NULL
,則回傳 false
,不執行任何操作。new_element_t
節點的記憶體,確保新節點可以存儲字串。s
到 element_t
的 value
,確保字串內容獨立存儲。new_element_t
,避免記憶體洩漏,並回傳 false
。list_add_tail
將新節點插入 head
之前,維持 queue
的環狀雙向鏈結串列結構。true
,確保操作結果可用於錯誤處理。Commit: 88e02fc
q_remove_head
以移除佇列的頭部元素。q_remove_head()
負責移除 queue
中的第一個元素,但不釋放記憶體,這符合 remove
與 delete
的區別。當提供輸出緩衝區時,函式會複製被移除的字串,並確保不會發生溢位。head
是否為 NULL
或 head
是否為空,若為 NULL
或 head->next
指向 head
則回傳 NULL
,避免錯誤操作。head->next
,即佇列的第一個 element_t
節點,作為要移除的節點。list_del
解除該節點與佇列的鏈結,使其脫離佇列,但不釋放其記憶體,符合 remove
而非 delete
的概念。sp
非 NULL
,則將 element_t->value
的字串複製至 sp
,最多 bufsize-1
字元,並在結尾補上 '\0'
。element_t
指標,以便後續使用或釋放。Commit: a7a18e0
q_remove_tail
以移除佇列的尾部元素q_remove_head
設計類似,只是 remove
的位置變成 queue
的 tail
。head
是否為 NULL
或 head
是否為空,若為 NULL
或 head->prev
指向 head
,則回傳 NULL
,避免錯誤操作。head->prev
,即佇列的最後一個 element_t
節點,作為要移除的節點。sp
非 NULL
,則將 element_t->value
的字串複製至 sp
,最多 bufsize-1
字元,確保字串結尾有 '\0'
。list_del
解除該節點與佇列的鏈結,使其脫離佇列,但不釋放其記憶體,以符合 remove
而非 delete
的概念。element_t
指標,以便後續使用或釋放。Commit: dbfc425
q_size
以獲取佇列中的元素數量。list_for_each
來遍歷節點,確保 O(n)
時間複雜度,並提升可讀性與可維護性。head
是否為 NULL
或 list_empty(head)
,若為 NULL
或空,則回傳 0
。number
為 0
,用於記錄 queue
中的元素數量。list_for_each
迭代 queue
中的所有節點,並遞增 number
記錄節點數量。number
,作為 queue
的長度。Commit: b05ef28
q_size
改進 q_delete_mid
以提升可讀性。O(n)
計算 queue
的長度,以確定中間索引,然後再以 O(n)
遍歷鏈結串列,確保能準確定位並刪除節點。head
是否為 NULL
或 queue
是否為空,若為 NULL
或 list_empty(head)
,則回傳 false
,不執行任何操作。q_size(head)
計算 queue
的長度,並儲存於 size
變數中。mid_index = size / 2
。for loop
找到中間節點,從 head->next
開始遍歷 mid_index
次,找到對應的節點。list_del(node)
解除該節點與 queue
的鏈結,確保不影響其他節點。element_t
節點的 value
記憶體,確保字串內容不會洩漏。element_t
本身,確保整個節點的記憶體被正確回收。true
表示刪除成功,確保函式運行結果可被上層函式使用。Commit: afcc69e
q_delete_dup
以刪除重複的元素queue
已經排序,所有重複節點都會是連續的,因此可以透過一次 O(n)
遍歷來完成 in-place
刪除,不需要額外的儲存空間。通常,在未排序的 queue
中,使用 hash table
來記錄出現過的字串能有效避免 O(n²)
的時間複雜度。然而,在此題目中,因為所有相同的字串已經是相鄰的,雜湊表並不能提供額外的優勢,直接遍歷 queue
來刪除相鄰的重複值即可達到最佳效率。head
是否為 NULL
或 queue
是否為空,如果是則直接回傳 false
,不執行任何操作。current
指標,指向 queue
的第一個元素 (head->next)
,準備開始遍歷 queue
。while (current != head)
迴圈,確保遍歷 queue
的所有元素。current
節點的內容 (element_t)
,準備檢查是否有重複的值。next
指標,指向 current->next
,並設置 duplicate = false
變數,用來追蹤 current
是否需要被刪除。while (next != head)
迴圈,檢查 current->value
是否與 next->value
相等,當相等就直接刪除該點,直到找到不一樣的節點。duplicate == true
,則表示 current
也是重複的,需要刪除:
list_del(current)
解除 current
節點與 queue
的鏈結。current
節點的記憶體 value
和 element_t
。current = next
,繼續遍歷 queue
,檢查下一個節點。Commit: 09fb11d
q_swap
以交換相鄰的節點queue
,將每對相鄰的節點進行交換,且不需要額外的記憶體配置。該方法能在 O(n)
的時間內完成操作。queue
是否為空或只有一個節點。first
和 second
指標。while (first != head && second != head)
,遍歷 queue
,每次交換一對節點。Commit: 673ac5e
q_reverse
以反轉 queue
內的元素順序。list_move
,每個節點依序移動到 queue
的前端,避免不必要的記憶體操作,並維持 O(n)
的時間複雜度。queue
是否為空或 NULL
:
head == NULL
或 list_empty(head)
,則 queue
無需反轉,直接返回。current
和 safe
指標,準備遍歷 queue
:
current
用來指向當前處理的節點。safe
用來暫存 current->next
,確保刪除或移動 current
後,仍能存取下一個節點。list_for_each_safe
遍歷 queue
,並將 current
節點移動至 head
之前:
list_move(current, head)
將當前 current
節點移動至 head
之後,使 queue
反轉。list_for_each_safe
確保遍歷時不會因為節點移動導致無限迴圈或錯誤存取。Commit: 70b0100
q_reverseK
以反轉 queue
中每 k
個節點的順序queue
是否包含完整的 k
個節點,以避免處理不完整的群組。透過 list_move
進行 in-place
反轉,使演算法達到 O(n)
時間複雜度,且不會進行額外的記憶體配置。queue
是否為空 NULL
,或 k
是否無效。 current
和 safe
指標,current
為當前要處理的節點,safe
用來確認當前 group
是否有足夠的 k
個元素可供反轉。queue
,每次處理 k
個節點。Commit: c01e743
q_sort
以排序 queue
中的元素,可選擇遞增或遞減排序q_sort()
,實作方式為 in-place sorting
,並且不會額外配置新的記憶體。但最差的情況下時間複雜度為 O(n^2)
。queue
是否為空或僅有單一節點。sorted
,用來存放排序後的節點:
INIT_LIST_HEAD(&sorted)
初始化 sorted
為空鏈結串列。queue
內還有節點時,執行以下步驟:head->next
,即 queue
的第一個節點,並使用 list_del(node)
將其從 queue
中移除。sorted
,找到正確的插入位置。node
尚未插入,則將其加入 sorted
的尾端。sorted
內的排序結果合併回 head
。Commit: 6fa239e
q_ascend
移除違反遞增順序的節點O(n)
解法,僅需掃描列表一次,並能立即釋放不必要的節點。queue
是否為 NULL
或空:
head == NULL
或 list_empty(head)
,則直接返回 0
。list_is_singular(head)
(僅有一個節點),則直接返回 1
。current = head->prev
,從 queue
尾端 開始遍歷。min_val
設定為 最後一個節點的值,作為目前遇到的最小值。 queue
:
current->value > min_val
:
current
右側存在比它更小的值,因此將 current
從 queue
中刪除。current_entry->value
和 current_entry
。current->value < min_val
:
min_val = current->value
,因為遇到了一個更小的元素。current->value == min_val
:
queue
中的元素數量 q_size(head)
。Commit: 11201ce
q_descend
以移除右側更大值的節點O(n)
最佳解,能夠立即釋放不必要的節點,並確保正確性。queue
是否為 NULL
或空:
head == NULL
或 list_empty(head)
,則直接返回 0
。list_is_singular(head)
(僅有一個節點),則直接返回 1
。current = head->prev
,從 queue
尾端 開始遍歷。max_val
設定為 最後一個節點的值,作為目前遇到的最大值。queue
:
current->value < max_val
:
current
右側存在比它更大的值,因此將 current
從 queue
中刪除。current_entry->value
和 current_entry
。current->value > max_val
:
max_val = current->value
,因為遇到了一個更大的元素。current->value == max_val
:
queue
中的元素數量 q_size(head)
。Commit: 5553243
目標: 使用 q_merge
合併多個已排序的佇列
設計想法:
list_splice_tail()
來確保高效合併。透過就地合併,該函數可保留原始佇列的內容,同時確保只有第一個佇列仍然包含元素。合併後會執行排序以維持順序,確保正確性。此函數的時間複雜度為 O(n log k)
,其中 k
為佇列數量。queue
是否為 NULL
或只有一個 queue
:
head == NULL
,則無法進行合併,直接返回 0
。list_is_singular(head)
(僅有一個 queue
),則直接回傳第一個 queue
的大小,無需合併。current = head->next
,從鏈結的第一個 queue
開始遍歷。first_queue_contex_t
為 head->next
,作為合併的目標 queue
。head
所指向的 queue
並合併:
current->next
開始遍歷,將所有 queue
合併至 first_queue_contex_t->q
。current_queue_contex_t->size > 0
,則執行:
list_splice_tail_init(current_queue_contex_t->q, first_queue_contex_t->q)
將當前 queue
連接到第一個 queue
的尾端。first_queue_contex_t->size
:
q_size(first_queue_contex_t->q)
來確保 queue
的長度資訊正確。queue
進行排序:
q_sort(first_queue_contex_t->q, descend)
來維持合併後的順序。queue
大小。