contributed by < chiangkd >
1
延伸問題:
提示: 善用 TreeComparison 製圖
在原本的實作的結構體中,包含親代節點、左子、右子節點,且使用 __attribute__((aligned(sizeof(long))))
使此結構體對齊 sizeof(long)
也就是讓最後兩個 bit 維持為 0
,在日後便可利用這兩個 bit 中其一用以標示顏色
而改寫後的結構體如下,此時親代節點已經不保存於 rb_node
中
在測試程式中, xprefix
為 tree_
,在經歷一連串的展開、串接後就會拿到具有 tree_
系列 prefix 的函式定義,例如測試程式中使用的 tree_new
、tree_insert
、tree_search
等等
老師貼文以及 wanghanchi 同學筆記
王漢祺 同學研究給定的紅黑樹實作,他著手理解為何描述節點的結構體只包含指向左側和右側的指標,卻沒有親代節點的指標,這是因為既然紅黑樹的樹高增長較慢,與其在描述節點的結構體中多佔用一個指標的空間 (與 WORD_SIZE 等寬,亦即 64 位元或 32 位元,取決於微處理器架構),不如挪動節點的走訪成為路徑。
以下圖為例,即將要插入的節點 key 為 90,於是節點走訪的過程可表示為:
- 先對 50 比較,得到 cmp = 1 後,就會往右側進行下一層的尋找,並紀錄 cmp
- 對 70 比較,一樣得到了 cmp = 1 後,就會往右邊進行下一層的尋找,並紀錄 cmp
- 再對 80 比較,得到 cmp = 1 後,就會往右邊進行下一層的尋找,這時會得到 NULL,並紀錄 cmp
- 跳出 for 迴圈,將 pathp 指向節點,亦即指向即將插入的節點
如此一來,即使描述節點的結構體不存在親代節點的指標,但運用上述路徑走訪,依然可完成插入的操作,也因此程式碼將這個特別的陣列命名成 path。
新增節點
在測試程式中 tree_insert
會透過建立好的隨機 nodes
陣列將其中的元素插入 tree
中。
查看其對應註解
以測試程式為例
x_attr
static
x_prefix
tree_
x_rbt_type
tree_t
x_type
node_t
x_field
link
(name of rb tree node linkage)x_cmp
node_cmp
其中 node_cmp
key
is search key,如果 a > b 則回傳 1
, a < b 則回傳 -1
,duplicate ,相等則回傳 0
在 tree_insert
中會先根據名稱定義的 tree_path_entry_t
型別的 path
,其最大長度為 RB_MAX_DEPTH
void *
為 8 bytes ,故 RB_MAX_DEPTH
將樹的最大深度限制在 bytesWind
在 tree_insert
中的 node
為要插入的節點,在第一個 for loop 中 (for (pathp = path; pathp->node; pathp++)
) 會去比較 node
和當前 tree 中的節點 pathp->node
,如果有相同的 key 則觸發 assert
。接著
cmp < 0
,即代表要放在左側作為左子節點,藉由 rbtn_left_get
獲取 pathp->node->link->left
link
由 rb_node(node_t)
產生,其結構體包含 node_t *
型別的 left
以及 right_red
cmp > 0
,即代表要放在右側作為右子節點,藉由 rbtn_right_get
獲取該節點指標,但是因其後兩位 bit 作為顏色標示為 1
(紅色),故額外使用 $ ~3
將其清除。將走訪過的節點 accessor 紀錄於 pathp[1]
中,且 cmp 的結果紀錄於 path->cmp
中,最後走訪完成後將待插入的節點 node
置於 pathp->node
。
利用兩個 assert 確認該 node
左右皆為 NULL
Unwind
接著在第二個 for loop 中倒著走訪回來 for (pathp--; (uintptr_t)pathp >= (uintptr_t)path; pathp--)
在迴圈中 cnode
為走訪中的 node
,接著照順序查找剛剛紀錄下來的 cmp
,如果 cmp < 0
rbtn_left_set
將 cnode
的左子節點設定為 left
,若其 rbtn_red_get(x_type, x_field, left)
不存在,也就是其右節點是黑色的則直接 return,代表結束leftleft
) 是否存在且右節點是否為紅色的,若真,則將 leftleft
塗黑並對著 cnode
進行右旋,在將 cnode
設定為 tnode
,也就是原來的親代節點的左子節點,作為新的親代節點 (迭代中的位置)。刪除節點
同樣地在測試程式中根據 NNODES
數量進行刪除
查看其對應註解
Wind
以下圖為例,刪除 49 節點
同樣的先走訪 tree
,紀錄其 cmp
值,在 else
區塊有判斷 cmp
是否為 0
,也就是找到要刪除的節點。
node | 48 |
60 |
50 |
49 |
---|---|---|---|---|
cmp | 1 | -1 | -1 | 0 => 1 (Find node's successor) |
在這邊也用了一個 assert
來檢查 nodep->node
是否指向要刪除的節點 node
pathp--
後 pathp
指向 49
因其只有一個節點且沒有子節點,會進到條件中
判斷到刪除的節點 49
為紅色節點,直接遺除掉就結束了
刪除完成
繼續下去此時若刪除節點 50
,一樣從 root 開始逐個進行 cmp
,建立表如下,
node | 48 |
60 |
50 |
51 |
NULL |
---|---|---|---|---|---|
cmp | 1 | -1 | 0 => 1(Find node's successor) | -1 |
在比對到 cmp == 0
時 (50
)
一進入時的 pathp
指向 50
,並將其 cmp
設定為 1
,path++
後會指向 51
,並將其 cmp
設定為 -1
,接著透過 rbtn_left_get()
將 path
最後一個填入 NULL
此時 pathp
指向 NULL
, nodep
指向 node
也就是 50
透過 pathp--
此時 pathp
指向 51
,進入條件 if (pathp->node != node) {
中,將 tred
為 1
rbtn_color_set
將 51
設定為黑色rbtn_left_set
將 51
的左子節點設定為 50
的左子節點,也就是 NULL
接下來
rbtn_right_set
會將 51
的右子節點設定為 50
的右子節點,也就是 51
自己rbtn_color_set
會將 50
設定成紅色此處有註解
正好符合現在的情況,他的 successor 就是他的右子節點 (51
)
接著把 nodep
和 pathp
指向的 node
交換,故此時 nodep->node
指向 51
, pathp->node
指向 50
,此時因 nodep[-1].cmp
為 -1
故將其左子節點設定為 nodep->node
,也就是將 60
的左子節點設定成 51
在此處把 out-of-date 的 pathp[-1]
(50
) 的左子節點設定為 NULL
刪除完成
此時在接著刪除 51
節點
Wind 部份與前述相似
node | 48 |
60 |
51 |
NULL |
---|---|---|---|---|
cmp | 1 | -1 | 0 => 1(Find node's successor) |
迭代完後來到刪除黑色節點,所以需要 Unwind 直到恢復平衡狀態,首先因刪除節點 (51
) 的前一個為 60
,其紀錄的 cmp
為 -1
,且在本例中最後會進到這個 case (和註解長的不太一樣?)
在 rbtn_rotate_left
中,會將 pathp->node
(60) 的右子節點設定為 80 的左子節點,並將 r_node
(80) 的左子節點設定為 pathp_node
(60)
做完這一步後會長這樣
但從上方這張圖片到下方刪除完成後的結果似乎沒有實作?
刪除完成結果應長的如下
突然注意到在註解中有提到標頭檔中的巨集實作為 left-leaning 2-3 red-black tree
如此一來舉上面這個例子就不太正確了,在簡報中有提到左傾紅黑樹,3-nodes 的轉換必須是左傾的,而因為是 2-3 tree,所以暫不考慮 4-node 的情況
在傳統的紅黑樹下這樣的樹的樣子是可以接受的,但在左傾的情況下不行
左傾紅黑樹必須確保左節點是紅色的,也就是左子樹要比右子樹還要高或相等,在簡報中也有提到一個 "surprise",也就是根據顏色交換 (color flip) 的位置不同可以將實作從 2-3-4 trees 變為 2-3 trees
這段也可以對應到在 insert
函式中的
在論文中也有相對應的敘述
2-3 trees Remarkably, moving the color flip to the end in the top-down 2-3-4 tree implementation just described yields an implementation for 2-3 trees. We split any 4-node that is created by doing a color flip, passing a red link up the tree, and dealing with the effects of doing so in precisely the same way as we move up the tree.
2
延伸問題:
3
延伸問題
1
對節點的紅色和黑色標注的技巧嵌入到指標變數中