contributed by < rain20010126
>
使用 Digit-by-digit calculation 方法找出輸入 的平方根
首先將 分解成由 n 個整數之和:
接著根據 ,將 展開如以下:
令 ,則,此時 即為所求的平方根
接著我們需要找出每個 的值,可以透過 ,其中 慢慢試到 , 透過檢查 是否成立來判斷 為 或是 ,但是此種方法的成本過高
若是定義 和 為以下
接著透過 ,可將上述式子轉換成以下
可以發現這時只需要紀錄 的值即可取代 的計算,最後將 拆解成 以及 ,詳細定義為以下
這時可以透過 來判斷 為何
接著藉由位元運算來進行下一輪的 , ,如以下
最後求得 即為最後的解
程式碼部分
int m = 1UL << ((31 - __builtin_clz(x)) & ~1UL)
使最小位元為 0 (確保其值為偶數,不理解為何)
由此可知程式碼中的 b
, z
, m
分別對應到 , , ,迴圈中的 AAAA
應該填上 2
, BBBB
填上 1
此題為進行 mod10 和 div10 操作時,如何改成使用 Hacker's Delight
的 bitwise operation 方式來做除法
因為 沒辦法用 所表示,因此使用 bitwise operation 的方式無法完全整除,此時只能找一個接近 的數字
由以下程式碼可以判斷出 tmp
的範圍為 0~19
這時透過 證明 可以得知若是要找到一除數 ,使 直到小數第一位都是精確的,則可列出 , 代入 19, 其中 (是十位數, 為個位數),則可得到以下
這時只要找到 滿足此範圍的數即可,即 ,也就是說將 x*1/10
近似為 x*13/128
原程式碼中的tmp/10
分解成 ,此時 就會等於 對應的程式碼為
接著再乘以 8
此時可以發現在進行 right shift 時會遺失最低的 3 個位元(餘數部分),所以需要預先保存最低的三個位元後加回去
最後除以 128 即完成求出商數的部分
求餘數的部分則簡單由 tmp
減去商數乘以 ,也就是
最後包裝成題目的程式碼如以下
首先 uint32_t x = (in | 1) - (in >> 2)
部分,當 in
為偶數時會需要做 +1
,
當 in
為奇數時,
接著 uint32_t q = (x >> 4) + x;
,當 in
為偶數時,
當 in
為奇數時,
此時若是 ,則 q = (q >> 8) + x;
可以等價為 q = x
,也就是 q
不變,假設 in
為奇數,(q >> 3)
會等價於 即可取得商數
若是 則會透過 q = (q >> 8) + x;
,假設 in
為奇數,可以將 逼近至
最後取餘數程式碼 *mod = in - ((q & ~0x7) + (*div << 1));
即為 *div * 10
,其中 q & ~0x7
目的是保留最後三個 bits 以外的位元,等同於 *div << 3
此題為計算 log2
,版本一即是將數字不斷右移來找到最高位元為 1 的位元,即為 log2
的答案,版本二則是透過二分搜尋法的方式找尋
或者直接使用 __builtin_clz
找到第一個 set bit
的位置,再由 31 減去他即可求得答案
EWMA 實作,其為一種取平均的統計方法,獲取資料所經過的時間越久則權重越低,以下為其定義
首先程式碼的結構體部分如以下,internal
是用來記錄當前時間的 ewma
,weight
為 ,由 vax-r 的說明讓我了解到 factor
的作用為定點數運算的縮放係數,用來控制小數點精度的,可以將原先為小數的數字放大
接著以下
若略過定點數運算 avg->factor
的影響,使 val
直接對應到 ,接著觀察程式碼可以得出以下,
由上可知
此題為計算 ,以下為程式碼
程式碼目標是找到輸入 x
的第一個 set bit 的位置,使用的方法類似 測驗三 透過依序檢查輸入 x
,先判斷 x 有無大於 ,若有則將 x
右移 16 個位元並紀錄 r
為最高位元的位置,其中因為 r
和 shift
都是 2 的指數,所以 r | shift
會等效為 r + shift
(r | shift | x > 1)
部分因為判斷 x > 0x3
時若是 x
為 0x2
或是 0x3
會少一,所以才需要 x > 1
,最前面需要執行 x--
是因為當 x
為 2 的冪次方時會比正確答案多一,所以需要先減一避免此情況發生
首先可以看到 XTree 的結構組成如下,hint
可由程式碼推測出其紀錄該節點的子樹高
void xt_destroy(struct xt_tree *tree)
: 遞迴呼叫 __xt_destroy()
將整棵樹刪除
struct xt_node *xt_first(struct xt_node *n)
: 尋找節點 n 子樹中最左側的節點
struct xt_node *xt_last(struct xt_node *n)
: 尋找節點 n 子樹中最右側的節點
static inline void xt_rotate_left(struct xt_node *n)
: 對節點 n 進行左旋,以下是左旋的說明
static inline void xt_rotate_right(struct xt_node *n)
: 對節點 n 進行右旋,以下是旋轉的方式
static inline int xt_balance(struct xt_node *n)
: 計算節點 n 的左右子樹差值
static inline void xt_update(struct xt_node **root, struct xt_node *n)
: 計算節點 n 的左右子樹的樹高差值 hint
後,當小於負一時對其進行右旋,大於一時進行左旋,最後當 hint
值有改變或是差值為 0 ,則繼續對 n
的親代節點執行 xt_update
int xt_insert(struct xt_tree *tree,void *key)
: 建立一個新節點並賦值為 key ,首先利用 __xt_find
找尋是否已經有相同 key 的節點,若有則取消插入,若不存在,則繼續判斷樹是否為空,若非空可則由 __xt_find
回傳找到 key
應擺放的位置進行插入並使用 xt_update
進行更新,若樹為空則建立一個樹並將新節點設定為根節點
int xt_remove(struct xt_tree *tree, void *key)
: 此為刪除值為 key 的節點,首先利用 xt_find
呼叫 __xt_find2
找出該節點位置,接著利用 __xt_remove
來移除該節點,以下為 __xt_remove
的說明
當要刪除的節點存在右子樹時,會先找到右子樹的最左節點,並使用 xt_replace_right
來替代 del
的位置,若右子樹不存且左子樹存在時,則找到左子樹中最右邊的節點,並使用 xt_replace_left
來替代 del
,若 del
為 leaf node 則直接刪除。
AVl Tree 的基本定義 :
AVL tree 保證左右子樹的高度差距不大於 1,與 Xtree 相同,不同的是 AVL tree 透過平衡因子(左子樹高度減去右子樹高度)判斷,而 Xtree 則是透過結構體中的 hint
儲存子樹高度,再分別利用左子樹及右子樹的 hint
來計算高度差,最後在高度差的絕對值大於一時對二元樹進行旋轉,恢復二元樹的平衡,而此兩種二元樹在插入並進行平衡時,至多皆不大於兩次旋轉,不同的地方是,AVL tree 在旋轉後只須更新受影響節點的平衡因子,而 Xtree 則須更新該節點的親代節點至根節點的樹高,AVL tree 在搜尋、插入以及刪除節點的時間複雜度皆為
紅黑樹的基本定義如下
以上性質確保了從根節點到任何葉節點的最常路徑步超過最短路徑的兩倍,另外根據 Red-Black Trees ,紅黑樹在插入的時間複雜度表現中,尋找、插入以及刪除節點的複雜度皆為
由以上結果可以得知紅黑數和 AVL tree 平均時間複雜度都是 ,其中 AVl tree(左右子樹的高度相差不超過 1)較紅黑樹(最長路徑 2 倍最短路徑)平衡,在搜尋以樹高作為上限的操作下,AVL 樹會比紅黑樹略快,但由於 AVL 樹比紅黑樹要平衡,因此在平衡自身所花費的時間上 AVL 樹所花費的時間較多,所以在插入以及移除的操作下紅黑數的表現較優,紅黑樹的優點在平衡自己時達到 (最多 3 次旋轉) 的複雜度
為了將 XTree 與紅黑樹以及 AVL 樹進行比較,我選擇先參考 var-x 完成的部份程式碼,包含新增 Linux 核心 red-black tree 以及修改測試程式碼 treeeint.c
接著我加入了以 AVL Tree Program in C 作為 AVL tree 的實作程式,新增了判斷樹高的函式,修改其 insert
的方法,其中遇到已經存在的數值則放棄該次的插入,並新增搜尋和刪除節點的函式
Linux 核心 red-black tree 與原始 XTree 的比較結果如下,我做了 100 0個與100 個循序輸入以及亂序輸入進行較,但在 1000 個亂序輸入中,我實作的 AVL tree 會因為過多遞迴呼叫造成 segmentation fault ,因此沒有紀錄該項結果,以下為比較實驗結果的比較
另外我新增了 find_rbtree_height.c
來判斷循序和亂序輸入 100 和 1000 個節點的最短路徑和最長路徑,其中亂序輸入所得到的輸入順序是藉由測試程式碼 treeint.c
所得到的,與其他二元樹的輸入相同
可以觀察到結果與前面所提到的優缺點有衝突,我推測是因為 Linux 核心的紅黑樹較一般紅黑樹少了一層的間接操作,並擁有更好的 cache locality,而我簡單實作的 AVL tree 上有很大的改進空間,造成實際測試無法呈現出 AVL tree 的優勢
由結構上的相似度判斷,XTree 較接近 AVL tree,XTree 利用 hint 來評估樹是否要旋轉,在AVL tree 中,為了判斷二元樹的平衡程度,這時可以利用平衡因子判別是左傾或右傾 ,計算方式為左子樹高度減去右子樹高度(深度),當平衡因子小於 -2 或大於 2 時,需要進行旋轉,共有四種旋轉方式分別為: LL、RR、LR、RL ,而我們可以觀察到在 XTree 中只有兩種旋轉方式,這時可以發現由節點 n 往上檢查左右子樹相差有無大於 1 ,檢查到節點 a 時發現其左子樹高較右子樹高 2,因此會對節點 a 進行右旋
對節點 a 進行旋轉後如下,可以發現節點 a 的左右子樹差沒有減少
因為 XTree 同樣是利用一個節點的左右子樹差來進行旋轉,使用 AVL Tree 的旋轉方式可以解決以上問題,也就是分成不平衡的類型 RR, RL, LR, LL 四種,並按照不同的情況進行相對應的旋轉,以下是修改的差異
接著我使用與先前相同的實驗方式對修正後的 XTree 進行比較,以下是比較的結果呈現
從以上亂數輸入的實驗結果中可以發現,相比於修改前的結果,修改過後的 Xtree 在樹高部份(Xtree hint)都有減少,在亂序輸入 100 個節點中 hint 從 7 下降為 6 ,而在亂序輸入 1000 個節點中 hint 從 11 下降為 10 ,另外我實際建立一個 Xtree 來更好的比較修改前及修改後樹的自平衡能力,其中我依序插入以下節點 [1000, 500, 300, 0, 400, 350, 450, 425, 475, 460]後
修正前的 XTree 結構如下
修改後的結構如下
修改前的樹高:6
修改後的樹高:4
透過以上結果可以發現解決了先前我們所提到的無效旋轉的問題,使 XTree 更加平衡,同時因為樹高的減少,在搜尋所花費的時間上也有大幅下降