contributed by < eleanorLYJ
>
有變數i,去模擬遞迴的stack的層數,用begin[i]
與 end[i]
代表 第i層考慮的左閉右閉的區間。每次把區間的第一個節點當pivot,接著從pivot->next 開始迭代向next走,直到把連 R 也走完
也就會把除了pivot的所有節點與pivot的值做比較或是說分類,節點大於pivot的加到right的開頭,反之與接到 left 的開頭。將所有節點分類完畢。
測驗寫的演算法,都是先考慮遞迴pivot左邊區間,之後將pivot存下,之後再遞迴pivot的右邊區間。
想要解決: 與pivot同樣的值的節點仍要繼續遞迴。
timsort 核心想法:
關於 run
關於合併
len(A) > len(B) + len(C)
len(B) > len(C)
原本以為run_size
函數寫錯,發現是把size 存在 節點->next->pre
裡。
閱讀程式碼後發現沒有作merge galloping, 我原本著手撰寫merge galloping, 但多加思考後是此演算法要實作在串列上, 然而普通合併方式與 merge galloping 效果會相同, 因此程式碼就維持不變
設計效能評比的測試程式與 Timsort 設想的資料分布樣式。
我測試資料設計成6種類別,分別為(0)全部隨機資料、(1)遞增資料、(2)遞減資料、(3)遞增資料並隨機交換3次、(4)遞增資料隨機交換7次、(5)每64個數字作為一個 run 組成的資料、(6)每64個數字作為一個 run 組成的資料並在同一個 run 以20%機率隨機交換 (7)全部資料以同一個隨機值組成
github
TODO:
前序可知哪個節點在上面 (越前面的越上面)、而由中序可知哪個節點靠左(越前面的越左邊)
觀察 dfs
函式如何遞迴的,其中著重理解參數意義。
首先知道,變數idx
是當前節點(tn
)在中序陣列中節點的索引值。
dfs
函式中的參數 in_low
與 in_high
比較好推測意義。觀察 tn->left
,
遞迴給tn
的左子樹,範圍 in_low
到 idx-1
(含)的中序索引範圍,而右子樹則是 idx+1
(含) 到 in_hight
以後的索引範圍。 也就是用 idx
分左右子樹的範圍.
接著觀察 tn->left
,遞迴給 tn
左子樹中的pre_low
的值是 pre_low + 1
,因為建節點順序是按前序的順序,而當前節點tn
已建值為preorder[pre_low]
的節點,因此 tn
的左子節點的值就是 preorder[pre_low+1]
。而tn
遞迴給左子樹的pre_high
是 pre_low + (idx - in_low)
,idx - in_low
是從中序觀點看目前 tn
的左子樹的節點個數。因此從pre_low
加左子樹的節點個數就是 tn
的左子樹要考慮的前序範圍。
我的結論:
pre_low
、pre_high
、in_low
和 in_high
參數是用於控制遞迴的範圍
pre_low
與 pre_high
代表當前遞迴要考慮的前序索引範圍(閉區間)。in_low
與 in_high
代表當前遞迴要考慮的中序索引範圍(閉區間)。在 Linux 核心的 cgroups 相關原始程式碼,找出 pre-order walk 程式碼並探討
kernel/cgroup/cgroup.c: * css_next_descendant_pre - find the next descendant for pre-order walk
find_nth_bit
指定的記憶體空間找出第 N 個設定的位元
__ffs
__ffs
找在word中第一個 bit (find first bit in word)
做法為逐步檢查數字中每個 2 的冪位元組,若該範圍內皆為 0,則將該範圍的位元數累加至 num 變數中,最後返回 num。若某個範圍內有 1 出現,則停止檢查並返回 num
帶入值確定做法。 (二進位的表示,我以每2的冪作區隔)
如果 64 位元全0 __ffs
就會得到 63
word = 0|00|0000|00000000|0000000000000000|00000000000000000000000000000000|0000000000000000000000000000000000000000000000000000000000000000
num = 32+16+8+4+2+1 = 63
若 32 位元中的第一個位元為 1,__ffs
將返回 30
word = 1|00|0000|00000000|0000000000000000|00000000000000000000000000000000 -> num = 16+8+4+2=30
若 32 位元中的第 1 和 2 位元為 1,__ffs
將返回 28
word = 1|10|0000|00000000|0000000000000000|00000000000000000000000000000000
->num = 16+8+4+0 = 28
能看出ffs
得到從右數數過來第一個1是出現在第 n 個位元為
__clear_bit
#define BIT_MASK(nr) (1UL << ((nr) % BITS_PER_LONG))
BIT_MASK 生成出遮罩,只有第(nr) % BITS_PER_LONG bit 為1 ,其餘為0的遮罩。其做法為將1往左位移 ((nr) % BITS_PER_LONG)) 位
#define BIT_WORD(nr) ((nr) / BITS_PER_LONG)
用來計算指定位元 nr 在位址的 word 索引。其做法為超過 BITS_PER_LONG
就回傳1 ,反之,小於BITS_PER_LONG就回傳0。
clear_bit
先取得遮罩,再判斷 nr
長度若超過BITS_PER_LONG
,addr
加一,得到指向nr
所在的位元組的指標 p
。接著將遮罩做位元翻轉(~mask
),再與 *p
做 AND 運作,將 位元nr
設為0。
這段程式碼定義了計算一個數字中設置為 1 的位元數量的巨集和函式。
__const_hweight8
計算一個 8 位元數字中設置為 1 的位元數量。這是通過對數字進行 8 次位元 AND 運算,每次都檢查該位元是否設置為 1,如果是則將結果加 1。最後,返回加總的結果。 這巨集裡的!!
能確保其結果為 0 或 1。
接著,__const_hweight16
使用 __const_hweight8
函式計算給定數字的前 8 位元和後 8 位元的位元數量,並將結果相加。
__const_hweight32
和 __const_hweight64
類似地使用 __const_hweight16
函式來計算給定數字的前 16 位元、前 32 位元和前 64 位元的位元數量,並將結果相加。
fns
fns
: 找第n個被設為1的位元位置 (find N'th set bit in a word)
while 迴圈內,變數 bit
的值 為 __ffs
函式找到 word 中最低位設為 1 的位元的位置,之後變 n 減一,接著使用 __clear_bit
清除bit
所在的位元,也是讓下一次迭代找到的最小位元是此次迭代的次小位元。然而當 n 為 0 就回傳當前最小位元。倘若n 大於 word 全部有被設為1的位元總量 或是 word 就是 0,兩情況就返回 BITS_PER_LONG
。
((~0UL) - (1UL << (l)) + 1)
: 創造一個 l 之前都是 1,之後全是 0 的二進位
例如 當 l = 5
二進位 = 1111111111111111111111111111111111111111111111111111111111100000
(~0UL >> (BITS_PER_LONG - 1 - (h))
: 創造一個第 h+1
之前位元都是 0,之後全是 1 的二進位。
例如 當 h = 5
二進位 = 00000000000000000000000000000000000000000000000000000000000111111
GENMASK 最終能得到介於 第 h 與 l 之間的位元為 1 其餘為 0 的遮罩。
BITMAP_LAST_WORD_MASK
~0UL 全部位元都設置為 1 的值。而 (-(nbits) & (BITS_PER_LONG - 1))決定要讓~0UL往右移多少。
首先我查 C Operator Precedence。為 unary minus的 - 會優先於 Bitwise AND,所以 (-(nbits) & (BITS_PER_LONG - 1))
先將 nbits
取二補數之後,再與 BITS_PER_LONG - 1
進行做 AND 運算。其中 BITS_PER_LONG - 1
為常數63 (二進位為 11111),所以這個 AND 運算只會保留-(nbits)
最後一個BITS_PER_LLONG長度的有效位元。
接著我帶入值,觀察此巨集效果
{nbits} -- {(nbits) & (BITS_PER_LONG - 1) (十進位)} -> {BITMAP_LAST_WORD_INDEX(nbits)}
0 -- 0(0) ->11111111111111111111111111111111
1 -- 111111(63) ->1
2 -- 111110(62) ->11
3 -- 111101(61) ->111
4 -- 111100(60) ->1111
5 -- 111011(59) ->11111
6 -- 111010(58) ->111111
7 -- 111001(57) ->1111111
8 -- 111000(56) ->11111111
9 -- 110111(55) ->111111111
設 n = -(nbits)
巨集會返回的值是
__builtin_constant_p
判斷其參數值是否在編譯時期就知道參數
不理解: idx是?