contribute by < david965154 >
1
在問題代號 CCCC
迴圈中,目的為:走過每一節點,若其成員 value
大於 pivot
成員的 value
,則分配置鏈結串列 right
,否則為 left
,因此若要走過每一節點,則需以 p = p->next
達成。
觀察最外圈 while
迴圈,可以看到關鍵的
這裡是讓 L
指向 begin[i]
,而 R
指向 end[i]
,下一個判斷式為 if (L != R)
,這裡可以看到當 L 及 R 為同一點即無須進入分配 right
及 left
的區塊,是迴圈結束的關鍵。因此近一步來看 begin[i] 及 end[i] 為何
可以看到其實 begin[i]
就是鏈結串列 left
,而因為 left
是指標,指著第一個節點,當然 end[i]
也就是指著鏈結串列 left
的尾部節點,因此要填入 list_tail(left)
, end[i + 2]
則填入 list_tail(right)
,如此一來,可以透過將原始鏈結串列,不斷透過與錨點(pivot)比較大小分左右鏈結串列,依序加入右鏈結串列、錨點再來左鏈結串列,達到 quick sort
的目的。
2
timsort.c
內函式達成目標
透過是否有下個節點來回傳該鏈結串列長度,與 queue.c
實作時使用的結構大同小異,但是會在函式 timsort
內先將雙向循環鏈結串列從尾部與頭部斷開成為非循環狀態,因此可以達成這樣的計算判斷式
再來說明 (head->next->prev)
理應要指向一 struct
,但是這裡卻強制轉型為 size_t
型態,因此要從後方函式 find_run
看起,該函式在執行完的最後執行了 head->next->prev = (struct list_head *) len;
,他把 size_t
len
轉型為 struct
list_head
,再將鏈結串列長度資訊包含在已經斷開的 (head->next->prev)
。這樣在初始放入亂數在鏈結串列時,直接順便計算數量,並包含在鏈結串列內,無須在需要時逐一走訪鏈結串列計算節點數。
補充 std::size_t
std::size_t can store the maximum size of a theoretically possible object of any type (including array).
這段就是用來 merge
兩個鏈結串列的,透過比較兩個鏈結串列最前方的節點並接上 head 達成。
tail
用途顯然是用來接續 head
指向的鏈結串列,因此初始會指向指標 head
,若以一般使用 pointer
的方法改寫:
這種寫法每次進迴圈皆須做一次比較,若改以第二種方法:在進入 for
迴圈時讓 head
有指向節點,而不是一個指標,因此先做一次 cmp(priv, a, b)
,讓他接上一節點再進入 for
迴圈。不過又會因此多寫一段,若改以指向指標的指標寫,那麼指標 head
與節點 next
接為指標,不必因為 tail
只能指向結構而分兩種情況。
這裡是將 list
裡的節點依序接上 tail
,最後再頭尾相連形成雙向循環鏈結串列。
這裡也是 merge
,不過是將輸入的單向鏈節串列 merge
後同時轉為雙向鏈節串列,當其中鏈結串列比較完後統一將剩餘單向鏈結串列接至 b
再透過函式 build_prev_link
將 b
後方之單向鏈節串列轉為雙向鏈節串列。
給定一 list
從頭開始選取兩個 list->next
與 list
,若後比前小 ( 即 list->next
比 list
小 ) 則 list
的指標 next
指向 list
原先的 prev
直到又遇到後比前大或 list
沒有 next
節點 ( 即 list->next
比 list
大 ) ,整段迴圈即停止,而若後比前大,則正常移動。同時,兩者在移動的同時皆會計算經過節點數量。
在最後 next 會指向 NULL
或是 與上述順序相反的第一節點
。
最後會產生一單向鏈結串列,而 head 指向其第一節點,的節點的指標 prev
將會指向 NULL
,而將鏈結串列長度資訊包含在不需要的 (head->next->prev)
,也就是第二個節點的指標 prev
。
最後透過 struct
pair
透過 head
及 next
指向此函式建立的單向鏈結串列及下一次的起點 next
( 即此次單向鏈結串列迴圈結束後的第一個節點 )。
這裡也是做 merge
,不過 merge
的兩鏈結串列結構較特殊,其中一鏈結串列為另一鏈結串列的首個節點使用 prev
指向其首個節點,因此有一鏈結串列的走訪方法為 at
往 next
方向移動,而另一鏈結串列為 at->prev
後往 next
方向移動。
兩鏈結串列合併後,將 merge
後之鏈結串列首個節點 prev
指向原先 at->prev
的 prev
(即其中一項鏈結串列之原本指向的 prev
) 指向的節點,再將鏈結串列第二個節點的 prev
指向鏈結串列長度。
這裡就是 merge
不同串鏈結串列,不過因為 merge
的對象必須相鄰,因此同時會合併第 2 條鏈結串列的情況下,這裡會選擇較短的先與第 2 條做合併,因此可以達到每次合併都盡量以最少長度合併 (因為合併時所需的最大比較次數為兩鏈結串列之最大長度) ,而避免每次需與越來越長的鏈結串列合併時最大比較次數被該鏈結串列支配的情況。
這裡是確保堆疊上的 run 長度保持平衡。該函式主要檢查堆疊頂端的 3 個 run 是否滿足以下原則:
如 的長度不大於 加 的總和(亦即 ),且 的長度小於 ,因此會選擇將 與 進行合併。甚至當需 merge 之鏈結串列超過 4 時 D 也會被納入考慮,如 。
這裡實際上就是應用各函式,分成四個部分
find_run
切分子鏈結串列,且在切的時候當切出超過 2 個子鏈結串列會以 merge_collapse
確保堆疊上的 run 長度保持平衡。merge
子鏈結串列 >=3
時皆以 merge_force_collapse
進行合併。build_prev_link
建立雙向鏈節並以 merge_final
合併剩餘二雙向鏈節串列。如此一來便合併完各雙向循環鏈結串列並結束函式。
1
在說明如何透過陣列的前序 (preorder) 和中序 (inorder) 形式,建立二元樹前,先來看一下儲存 hash table 的鏈結串列 (不過在這裡沒有用到) 。
hlist_node
就是一個由成員 : 指向結構的指標 next
及指向指向結構的指標的指標 pprev
的結構。
詳見 Linux 核心的 hash table 實作
這裡需要添加 n
指向的 struct hlist_node
至 h->first
指向的首個節點,而因為指標的指標 pprev
指向前一個節點指向下一個節點的指標 (即指向自身節點的指標) 因此若 h->first
有指向節點,則先將原始 h->first
指向的首個節點的 pprev
指向要新增的節點的 next
指標,為 "退位" 做準備,接下來把 n->next
指向 h->first
和 n->pprev
指向 h->first
的位置,最後將 h->first
指向 n
即完成新增節點至鏈結串列的動作。
接下來就是整段程式碼核心,建立二元樹。
簡單明瞭就是透過 preorder
及 inorder
陣列順序尋找對應位置關係建立二元樹,而 pre_low, pre_high, in_low, in_high
是用來儲存須建立的子樹在陣列中的範圍。
2
這裡先創建一個 capacity 數量的 struct list_head
不過這裡的 malloc
配置應該有誤
LRUCache
結構為
應要配置 struct hlist_head hhead[]
的空間,且在迴圈內部
這裡會透過函式 INIT_HLIST_HEAD
將 capacity 個 &cache->hhead[i]
初始化,而 INIT_HLIST_HEAD
需要 struct hlist_head *h
作為參數,因此 malloc 應該改成
這裡就是將所有配置的 cache
做移去並釋放記憶體的動作。
給予一 key
值,若在 cache 中找到便移去 obj->dhead
並回傳其 value
。用意是只要最近使用到就移去最近在使用之代表區域。
這裡前面類似函式 lRUCacheGet
,不過這裡是在給予一 key
值,找到對應的 cache
,如果找布袋那就代表需要配置一節點給予放置,因此會判斷 count
是否等於 capacity
,若是則刪除最近沒使用到的,若非則配置一塊新的 LRUNode
,再將其 value 設為傳入參數 value
。
3
這裡要找一塊 bit 數為 size 的記憶體區塊,裡面第 num 個被 set 為 1 的位置,而計算時需要將 size 切分為多個 BITS_PER_LONG
再用 macro hweight_long
計算。
計算多少個連續 0 ,即為右移連續零後的第一個 1 之位置。
清除位於指定記憶體位址中指定位的位元值(設置為 0)
在 word (一個長整型數值) 中找到第 n 個設置位 (1 的位) 的位置。使用 while
迴圈不斷透過 __ffs
尋找,直到 n
為 0,結束。
使用上面的函式計算,若小於 BITS_PER_LONG
則無需使用函式 FIND_NTH_BIT
計算,直接對指向記憶體位置之值使用 macro GENMASK
產生長度為 size 的 mask,並使用 fns
計算。