紅黑樹是平衡二叉樹的一種,它有很好的性質,樹中的結點都是有序的,而且因為它本身就是平衡的,所以查詢也不會出現非常惡劣的情況,基於二叉樹的操作的時間複雜度是o(log(n))
。linux
核心在管理vm_area_struct
時就是採用了紅黑樹來維護記憶體塊的。
先到include/linux/rbtree.h
中看一下紅黑樹的一些定義,如下:
struct rb_node
__attribute__((aligned(sizeof(long))));
struct rb_root
只是struct rb_node*
的乙個包裝,這樣做的好處是看起來不用傳遞二級指標了。不錯,很簡單。再看一下下面幾個重要的巨集,細心的你一定會發現,rb_parent_color
其實沒那麼簡單,andrea arcangeli
在這裡使用了乙個小的技巧,不過非常棒。正如名字所暗示,這個成員其實包含指向parent
的指標和此結點的顏色!它是怎麼做到的呢?很簡單,對齊起了作用。既然是sizeof(long)
大小的對齊,那麼在ia-32
上,任何rb_node
結構體的位址的低兩位肯定都是零,與其空著不用,還不如用它們表示顏色,反正顏色就兩種,其實一位就已經夠了。
這樣,提取parent
指標只要把rb_parent_color
成員的低兩位清零即可:
#define rb_parent(r) ((struct rb_node *)((r)->rb_parent_color & ~3))
取顏色只要看最後一位即可:
#define rb_color(r) ((r)->rb_parent_color & 1)
測試顏色和設定顏色也是水到渠成的事了。需要特別指出的是下面的乙個內聯函式:
static inline void rb_link_node(struct rb_node * node, struct rb_node * parent, struct rb_node ** rb_link);
它把parent
設為node
的父結點,並且讓rb_link
指向node
。我們把重點集中在lib/rbtree.c
1. 每個結點要麼是紅色要麼是黑色;
2. 根結點必須是黑色;
3. 紅結點如果有孩子,其孩子必須都是黑色;
4. 從根結點到葉子的每條路徑必須包含相同數目的黑結點。
這四條規則可以限制一棵排序樹是平衡的。
__rb_rotate_left
是把以root
為根的樹中的node
結點進行左旋,__rb_rotate_right
是進行右旋。這兩個函式是為後面的插入和刪除服務,而不是為外部提供介面。
新插入的結點都設為葉子,染成紅色,插入後如果破壞了上述規則,通過調整顏色和旋轉可以恢復,二叉樹又重新平衡。插入操作的介面函式是
void rb_insert_color(struct rb_node *node, struct rb_root *root);
它把已確定父結點的node
結點融入到以root
為根的紅黑樹中,具體演算法的分析可以參考[1]中 第14.3
節,這裡的實現和書中的講解幾乎完全一樣。怎麼確定node
的父結點應該在呼叫rb_insert_color
之前通過手工迭帶完成。值得指出的一點是,雖然插入操作需要乙個迴圈迭代,但是總的旋轉次數不會超過兩次!所以效率還是很樂觀的。
刪除操作多多少少都有點麻煩,它要先執行像普通二叉查詢樹的「刪除」,然後根據刪除結點的顏色來判斷是否執行進一步的操作。刪除的介面是:
void rb_erase(struct rb_node *node, struct rb_root *root);
其實它並沒有真正刪除node
,而只是讓它和以root
為根的樹脫離關係,最後它還要判斷是否呼叫__rb_erase_color
來調整。具體演算法的講解看參考[1]中第13.3
和14.4
節,__rb_erase_color
對應書中的rb-delete-fixup
,此處的實現和書上也基本上一致。
其餘的幾個介面就比較簡單了。
struct rb_node *rb_first(struct rb_root *root);
在以root
為根的樹中找出並返回最小的那個結點,只要從根結點一直向左走就是了。
struct rb_node *rb_last(struct rb_root *root);
是找出並返回最大的那個,一直向右走。
struct rb_node *rb_next(struct rb_node *node);
返回node
在樹中的後繼,這個稍微複雜一點。如果node
的右孩子不為空,它只要返回node
的右子樹中最小的結點即可;如果為空,它要向上查詢,找到迭帶結點是其父親的左孩子的結點,返回父結點。如果一直上述到了根結點,返回null
。struct rb_node *rb_prev(struct rb_node *node);
返回node
的前驅,和rb_next
中的操作對稱。
void rb_replace_node(struct rb_node *victim, struct rb_node *new, struct rb_root *root);
用new
替換以root
為根的樹中的victim
結點。紅黑樹介面使用的乙個典型例子如下:
static inline struct page * rb_search_page_cache(struct inode * inode,
unsigned long offset)
return null; }
static inline struct page * __rb_insert_page_cache(struct inode * inode,
unsigned long offset,
struct rb_node * node)
rb_link_node(node, parent, p);
return null; }
static inline struct page * rb_insert_page_cache(struct inode * inode,
unsigned long offset,
struct rb_node * node)
因為紅黑樹的這些良好性質和實現中介面的簡易性,它被廣泛應用到核心程式設計中,大大提高了核心的效率。
紅黑樹核心原理
紅黑樹是一種近似平衡的二叉查詢樹,它能夠確保任何乙個節點的左右子樹的高度差不會超過二者中較低那個的一陪。具體來說,紅黑樹是滿足如下條件的二叉查詢樹 binary search tree 每個節點要麼是紅色,要麼是黑色。根節點必須是黑色 紅色節點不能連續 也即是,紅色節點的孩子和父親都不能是紅色 對於...
核心紅黑樹使用範例
核心中的紅黑樹只是提供了乙個管理機制,並沒有提供具體的使用介面。需要使用者根據自己的使用環境去定義和實現自己的關鍵字 char,uchar,int,uint等型別 操作。這樣可以更加靈活。像核心中的鍊錶,hash表的 都是這種思想。1 struct mytype 2 78 struct mytype...
linux紅黑樹實現
linux核心紅黑樹的演算法都定義在linux 2.6.38.8 include linux rbtree.h和linux 2.6.38.8 lib rbtree.c兩個檔案中。1 結構體 cpp view plain copy print?struct rb node attribute alig...