contributed by < POCHUN-CHEN
>
2023q1 第 1 週測驗題
struct item
為參考 list.h內的結構體,負責存值與順序的 list
分開寫,往後可以運用 contain_of
。
為什麼要用 static inline
?
->主要目的為提升程式速度。
the program behaves the same as if you had not used the inline keyword, except for its speed.
用以對節點內含值比較的函式:
cmpint
函數返回的整數值代表了比較 p1
和 p2
這兩個指向 uint16_t
數據的指標的結果。這種方式是用於標準庫函數 qsort 中,qsort 函數會根據 cmpint 函數的返回值來對數組進行排序。
比較結果 | 回傳 | Column 3 |
---|---|---|
*i1 大於 *i2 |
正整數 | p1 排在 p2 後面 |
*i1 等於 *i2 |
0 | 排序不變 |
*i1 小於 *i2 |
負整數 | p1 排在 p2 前面 |
這樣可以實現對任意類型(因為有 (const uint16_t *)
指標轉型)的數據進行排序的功能。
Q: 為什麼要把兩個指標函式傳進 cmpint
函式,再用 i1
、 i2
來被賦值呢?
p1
從指向 void 顯示轉型成(const uint16_t *)
(這邊是指轉換成指向const uint16_t
的指標,會有這種括號寫法,是因為p1
本來就是指標了)。
再賦值給指向const uint16_t
的i1
。
宣告i1
、i2
才不會影響到本來的。
上述問題
顯示指標轉型不會影響到原本 p1
、 p2
的值,因此這樣寫應該更精簡?
顯式的指標型別轉換會更改指標指向型別的編碼方式嗎?
顯式的指標型別轉換通常不會更改指標指向型別的編碼方式。指標的編碼方式(如指標的位元組長度、對齊方式等)是由編譯器和操作系統決定的,與指標指向的型別無關。因此,進行顯式的指標型別轉換時,被指向的二進制內容不會改變,只是指標的型別被視為不同的型別。把顯示指標轉型的指標解構後,會將指向的型別的二進位數值,利用轉換後的型別方式顯示值(可能會與原本不同,但是二進位的部分是一樣的,但有可能長度不同)
首先,來看看 list_sort
的程式碼
先檢查串列是否為空,或者是只有一個值(就不用排了)。
接下來建立 list_less
、 list_greater
,等等來存放,比原串列中某個數值大 / 小的數值們。
接下來 INIT_LIST_HEAD
,負責把新建立的 list_head
初始化,一定要把 prev
/ next
的前 / 後指標定義。
補充說明,以 list_less
為例, &list_less
與 &(list_less.prev)
是同樣地址,因為結構體 list_head
內是按照 *prev
、 *next
排序,所以第一個結構的起始地址就代表結構體的地址。
是否可善用 Micro 來簡化程式碼?作法如下。
由於 LIST_HEAD()
本身就包含宣告、初始化,故可以簡化程式碼。
LIST_HEAD(list_less)
= struct list head list_less
+ INIT_LIST_HEAD(&list_less)
。
接下來就要把剛剛 list_sort
函式的輸入參數引進來比較排序。這時候 item
結構可以用來包含順序串列與其節點內該有的值。之後我們就要來看 list_first_entry()
代表什麼意思,參考list.h
中,可得到以下定義。
這時可以查詢到 list_entry
是 container_of
等價的包裝,符合以 list_ 開頭的命名慣例,此處的 entry 就是 list 內部的節點。
特別注意:在 list_
系列的 member
都限定在 list_head member
。
如果 list_
系列的 member
,故意輸入 struct
中,不是 list_head member
的成員,會不會發生錯誤?
接著了解 container_of(ptr, type, member)
的實作手法。
程式碼是從 struct 中的 member 推算出原本 struct 的位址。
member
的指標這裡需要討論一下細節,由目前的程式碼中,我們只知道,我們把 list_head
結構的 head
傳進來 list_sort
函式。但熟知 linux 風格的 linked list ,我們知道在這個未經過排序的串列中,也是由 item
結構,包裹著決定串列順序的 list_head
linked list 結構。並且第一個 list_head
沒有被包含在 item
結構。
由此我們可以知道, list_first_entry(head, AAA, BBB)
可以看成 container_of((head)->next, type, member)
,藉此得到原本串列的第一個 item
記憶體位置。再將其賦值給 pivot
。
由 container_of(ptr, type, member)
可知,
item
。list
(因為我們知道linux 風格的 linked list ,是由 item
結構包裹,所以成員函數可以用 item
內部的 struct list_head list
)。再將 pivot
利用 list_del(&pivot->list)
從 head
這個串列中移除。
這邊
list_del(&pivot->list)
應該也可以寫成list_del(&head->next)
但是這樣很不直觀,我們都寫了pivot
,就直接專注在該節點就好。
觀察到後面出現 cmpint
比較大小的函示,可以推斷 CCC 應該為推進整個串列比較的迴圈函式。為了在宣告完 pivot
後,確保可以比較到串列中的每一個值,再參考輸入參數後,使用函式 list_for_each_entry_safe
。
list_for_each_entry_safe
。透過額外的 safe
紀錄每個迭代 (iteration) 所操作的節點的下一個節點,因此目前的節點可允許被移走,其他操作則同為不可預期行為。
當把 head
串列中的值比較大小後,移動到 list_less
/ list_greater
的尾部,使用函式 list_move_tail
。
list_move_tail
。list_move_tail
。這邊可以看出遞迴的結構,把原本串列按照數值大小分類。依序持續切到排序完成。
比較Merge Sort實作。
當分類完比 pivot
大或小所組成的兩串列後,且經由迭代即可完成排序。將 pivot
與排序後的串列,依序串成與原先串列長度一致的新串列。
將 pivot
加入 list 中的 head , list_less
用 list_splice
加在 head
後; list_gearter
用 list_splice_tail
加在 head
前。
list_splice_tail
。補充資料:
Timsort是一種在排序演算法中廣泛使用的混合排序演算法。它由Tim Peters於2002年開發,並在Python中成為預設排序演算法。Timsort結合了插入排序和歸併排序的優點,因此具有高效的排序速度和佔用較小的內存空間。
Timsort的基本思路是將數組分為若干個小塊,對每個小塊進行插入排序,然後將這些小塊進行歸併排序。在實際應用中,Timsort還有一些優化措施,例如檢測已經有序的塊,跳過已經有序的塊,使用二分查找尋找插入位置等等。
Timsort的時間複雜度為O(n log n),在最壞情況下的時間複雜度為O(n log n),空間複雜度為O(n)。Timsort是一種穩定的排序演算法,可以處理大規模的數據集,被廣泛應用於Java、Python等編程語言的標準庫中。
head
has more than two entry and not empty. If not return it.INIT_LIST_HEAD
to initialize two list structure we made.list_first_entry
to get first entry to set it as pivot.list_del
)strncmp
to compare item->value
to pivot->value
, and seperate them to each list_less
and list_greater
.q_sort
to regress list_less
& list_greater
.list_less
before pivot in list.list_greater
after pivot in list.參考 list.h
由程式碼可知GGGG,此程式碼與遞迴版本對應,在比對pivot
前,必需先確保可以比較到串列中的每一個值,使用函式list_for_each_entry_safe
。
當把串列中的值補在list_less
和list_greater
的尾部,使用函式list_move_tail
。
list_for_each_entry_safe
。list_move_tail
。&stack[top--]
。&stack[top--]
。XXX
。MAX_DEPTH
xorlist_destroy
,使其不急著釋放記憶體,而搭配 atexit(3) 並在程式即將結束執行時,才批次處理資源釋放