Linux核心紅黑樹1—Documentation/rbtree.txt翻譯
1. 什麼是紅黑樹,它們有什麼用?
------------------------------------------------
紅黑樹是一種自平衡二叉搜尋樹,用於儲存可排序的 鍵/值 資料對。 這不同於 基數樹(用於有效地儲存稀疏陣列,因此使用長整數索引來插入/訪問/刪除節點)和雜湊表(不保持排序以便於按順序遍歷,並且必須針對 特定大小和雜湊函式,其中 rbtrees 可以優雅地擴充套件儲存任意鍵)。
紅黑樹類似於 AVL 樹,但為插入和刪除提供更快的實時有界最壞情況效能(最多分別旋轉兩次和三次,以平衡樹),速度稍慢(但仍然是 O(log n)) 查詢時間。
引用 Linux Weekly News:
核心中使用了許多紅黑樹。 Deadline 和 CFQ I/O 排程器使用 rbtrees 來跟蹤請求; 資料包 CD/DVD 驅動程式也做同樣的事情。 高解析度計時器程式碼使用 rbtree 來組織未完成的計時器請求。 ext3 檔案系統在紅黑樹中跟蹤目錄條目。 虛擬記憶體區域 (VMA)
使用紅黑樹進行跟蹤,“分層令牌桶”排程程式中的 epoll 檔案描述符、加密金鑰和網路資料包也是如此。
本文件介紹了 Linux rbtree 實現的使用。 有關紅黑樹的性質和實施的更多資訊,請參閱:
Linux Weekly News 關於紅黑樹的文章: http://lwn.net/Articles/184495/
維基百科關於紅黑樹的條目: http://en.wikipedia.org/wiki/Red-black_tree
2. 紅黑樹的Linux實現
-------------------------------
Linux 的 rbtree 實現存在於檔案 “lib/rbtree.c” 中。 要使用它要 “#include <linux/rbtree.h>”。
Linux rbtree 實現針對速度進行了優化,因此比更傳統的樹實現少了一層間接(和更好的快取區域性性)。 不是使用指標來分隔 rb_node 和資料結構,而是將 struct rb_node 的每個例項嵌入到它組織的資料結構中。 而不是使用比較回撥函式指標,使用者應該編寫自己的樹搜尋和插入函式,這些函式呼叫提供的 rbtree 函式。 鎖定也由 rbtree 程式碼的使用者決定。
(1) 建立一個新的 rbtree
---------------------
rbtree 樹中的資料節點是包含 struct rb_node 成員的結構:
struct mytype { struct rb_node node; char *keystring; };
在處理指向嵌入式結構 rb_node 的指標時,可以使用標準的 container_of() 巨集訪問包含的資料結構。 此外,可以通過 rb_entry(node, type, member) 直接訪問單個成員。
每個 rbtree 的根是一個 rb_root 結構,它通過以下方式初始化為空:
structrb_root mytree = RB_ROOT;
(2) 在 rbtree 中搜索值
----------------------------------
為您的樹編寫搜尋函式相當簡單:從根開始,比較每個值,並根據需要遵循左側或右側分支。
例子:
struct mytype *my_search(struct rb_root *root, char *string) { struct rb_node *node = root->rb_node; while (node) { struct mytype *data = container_of(node, struct mytype, node); int result; result = strcmp(string, data->keystring); if (result < 0) node = node->rb_left; else if (result > 0) node = node->rb_right; else return data; } return NULL; }
(3) 將資料插入 rbtree
-----------------------------
在樹中插入資料涉及首先搜尋插入新節點的位置,然後插入節點並重新平衡(“重新著色”)樹。
插入的搜尋與之前的搜尋不同,它查詢要嫁接新節點的指標的位置。 為了重新平衡,新節點還需要一個到其父節點的連結。
例子:
int my_insert(struct rb_root *root, struct mytype *data) { struct rb_node **new = &(root->rb_node), *parent = NULL; /* Figure out where to put new node */ while (*new) { //*new為root->rb_node,然後往下遍歷,直到指向的為NULL退出迴圈 struct mytype *this = container_of(*new, struct mytype, node); int result = strcmp(data->keystring, this->keystring); parent = *new; if (result < 0) new = &((*new)->rb_left); else if (result > 0) new = &((*new)->rb_right); else return FALSE; } /* Add new node and rebalance tree. */ rb_link_node(&data->node, parent, new); rb_insert_color(&data->node, root); return TRUE; }
(5) 刪除或替換 rbtree 中的現有資料
------------------------------------------------
要從樹中刪除現有節點,請呼叫:
例子:
void rb_erase(struct rb_node *victim, struct rb_root *tree); struct mytype *data = mysearch(&mytree, "walrus"); if (data) { rb_erase(&data->node, &mytree); myfree(data); }
要將樹中的現有節點替換為具有相同鍵的新節點,請呼叫:
void rb_replace_node(struct rb_node *old, struct rb_node *new, struct rb_root *tree);
以這種方式替換節點不會對樹重新排序:如果新節點與舊節點的鍵不同,則 rbtree 可能會損壞。
(6) 遍歷儲存在 rbtree 中的元素(按排序順序)
--------------------------------------------------
提供了四個函式來按排序順序迭代 rbtree 的內容。 這些適用於任意樹,不需要修改或包裝(除了鎖定目的)::
struct rb_node *rb_first(struct rb_root *tree); struct rb_node *rb_last(struct rb_root *tree); struct rb_node *rb_next(struct rb_node *node); struct rb_node *rb_prev(struct rb_node *node);
要開始迭代,請使用指向樹根的指標呼叫 rb_first() 或 rb_last(),這將返回指向包含在樹的第一個或最後一個元素中的節點結構的指標。 要繼續,請通過在當前節點上呼叫 rb_next() 或 rb_prev() 來獲取下一個或上一個節點。 當沒有更多節點時,這將返回 NULL。
迭代器函式返回一個指向嵌入結構 rb_node 的指標,可以使用 container_of() 巨集從中訪問包含的資料結構,並且可以通過 rb_entry(node, type, member)直接訪問各個成員。
例子:
struct rb_node *node; for (node = rb_first(&mytree); node; node = rb_next(node)) printk("key=%s\n", rb_entry(node, struct mytype, node)->keystring);
3. 快取的 rbtrees
--------------
計算最左邊(最小)節點對於二叉搜尋樹來說是一項非常常見的任務,例如對於遍歷或使用者依賴於他們自己的邏輯的特定順序。 為此,使用者可以使用 'struct rb_root_cached'
將 O(logN) rb_first() 呼叫優化為簡單的指標獲取,從而避免潛在的昂貴的樹迭代。 這是在維護的執行時開銷可以忽略不計的情況下完成的; 儘管記憶體佔用更大。
類似於 rb_root 結構,快取的 rbtrees 被初始化為空通過::
struct rb_root_cached mytree = RB_ROOT_CACHED;
快取的 rbtree 只是一個普通的 rb_root,帶有一個額外的指標來快取最左邊的節點。 這允許 rb_root_cached 存在於 rb_root 所在的任何地方,這允許支援增強樹以及僅幾個額外的介面:
struct rb_node *rb_first_cached(struct rb_root_cached *tree); void rb_insert_color_cached(struct rb_node *, struct rb_root_cached *, bool); void rb_erase_cached(struct rb_node *node, struct rb_root_cached *);
插入和擦除呼叫都有各自對應的增強樹:
void rb_insert_augmented_cached(struct rb_node *node, struct rb_root_cached *, bool, struct rb_augment_callbacks *); void rb_erase_augmented_cached(struct rb_node *, struct rb_root_cached *, struct rb_augment_callbacks *);
4. 支援增強型 rbtrees
-----------------------------
增強的 rbtree 是一個每個節點中儲存了“一些”附加資料的 rbtree,其中節點 N 的附加資料必須是以 N 為根的子樹中所有節點內容的函式。此資料可用於增強一些新功能到rbtree。 增強的 rbtree 是一個構建在基本 rbtree 基礎設施之上的可選功能。 需要此功能的 rbtree 使用者必須在插入和刪除節點時使用使用者提供的增強回撥來呼叫增強函式。
實現增強 rbtree 操作的 C 檔案必須包含 <linux/rbtree_augmented.h> 而不是 <linux/rbtree.h>。 請注意, linux/rbtree_augmented.h公開了一些您不希望依賴的 rbtree 實現細節; 請堅持那裡記錄的 API,並且不要在標頭檔案中包含 <linux/rbtree_augmented.h> 以儘量減少您的使用者意外依賴此類實現細節的機會。
在插入時,使用者必須更新通向插入節點的路徑上的增強資訊,然後像往常一樣呼叫 rb_link_node() 和 rb_augment_inserted() 而不是通常的 rb_insert_color() 呼叫。 如果 rb_augment_inserted() 重新平衡 rbtree,它將回調到使用者提供的函式中,以更新受影響子樹的增強資訊。
擦除節點時,使用者必須呼叫 rb_erase_augmented() 而不是 rb_erase()。 rb_erase_augmented() 回撥使用者提供的函式以更新受影響子樹的增強資訊。
在這兩種情況下,回撥都是通過 struct rb_augment_callbacks 提供的。 必須定義 3 個回撥:
- 傳播回撥,它更新給定節點及其祖先的增強值,直到給定的停止點(或 NULL 以一直更新到根)。 - 複製回撥,將給定子樹的增強值複製到新分配的子樹根。 - 樹旋轉回調,將給定子樹的增強值複製到新分配的子樹根,並重新計算前子樹根的增強資訊。
rb_erase_augmented() 的編譯程式碼可能會內聯傳播和複製回撥,這會導致函式很大,因此每個增強的 rbtree 使用者應該有一個 rb_erase_augmented() 呼叫站點,以限制編譯程式碼的大小。
示例用法
^^^^^^^^^^^^^
Interval tree 是 增強rbtree 的一個例子。 參考 - Cormen、Leiserson、Rivest 和 Stein 的“演算法簡介”。有關區間樹的更多詳細資訊:
經典的 rbtree 有一個單一的鍵,它不能直接用於儲存區間範圍,如 [lo:hi] 並快速查詢與新 lo:hi 的任何重疊或查詢是否有新 lo 的 lo:hi 的精確匹配。
但是,可以擴充 rbtree 以以結構化的方式儲存此類間隔範圍,從而可以進行有效的查詢和精確匹配。
每個節點中儲存的這個“額外資訊”是其後代節點中的最大 hi (max_hi) 值。 只需檢視節點及其直接子節點即可在每個節點上維護此資訊。 這將用於 O(log n) 查詢最低匹配(所有可能匹配中的最低起始地址),例如:
struct interval_tree_node *interval_tree_first_match(struct rb_root *root, unsigned long start, unsigned long last) { struct interval_tree_node *node; if (!root->rb_node) return NULL; node = rb_entry(root->rb_node, struct interval_tree_node, rb); while (true) { if (node->rb.rb_left) { struct interval_tree_node *left = rb_entry(node->rb.rb_left, struct interval_tree_node, rb); if (left->__subtree_last >= start) { /* * 左子樹中的一些節點滿足 Cond2。 迭代找到最左邊的這樣的節點 N。如果它也滿足 Cond1, * 那就是我們正在尋找的匹配項。 否則,因為 N 右邊的節點也不能滿足 Cond1,所以沒有匹配區間。 */ node = left; continue; } } if (node->start <= last) { /* Cond1 */ if (node->last >= start) /* Cond2 */ return node; /* node is leftmost match 滿足在[start, last]區間裡*/ if (node->rb.rb_right) { node = rb_entry(node->rb.rb_right, struct interval_tree_node, rb); if (node->__subtree_last >= start) continue; } } return NULL; /* No match */ } }
插入/刪除是使用以下增強回撥定義的:
static inline unsigned long compute_subtree_last(struct interval_tree_node *node) { unsigned long max = node->last, subtree_last; if (node->rb.rb_left) { subtree_last = rb_entry(node->rb.rb_left, struct interval_tree_node, rb)->__subtree_last; if (max < subtree_last) max = subtree_last; } if (node->rb.rb_right) { subtree_last = rb_entry(node->rb.rb_right, struct interval_tree_node, rb)->__subtree_last; if (max < subtree_last) max = subtree_last; } return max; } static void augment_propagate(struct rb_node *rb, struct rb_node *stop) { while (rb != stop) { struct interval_tree_node *node = rb_entry(rb, struct interval_tree_node, rb); unsigned long subtree_last = compute_subtree_last(node); if (node->__subtree_last == subtree_last) break; node->__subtree_last = subtree_last; rb = rb_parent(&node->rb); } } static void augment_copy(struct rb_node *rb_old, struct rb_node *rb_new) { struct interval_tree_node *old = rb_entry(rb_old, struct interval_tree_node, rb); struct interval_tree_node *new = rb_entry(rb_new, struct interval_tree_node, rb); new->__subtree_last = old->__subtree_last; } static void augment_rotate(struct rb_node *rb_old, struct rb_node *rb_new) { struct interval_tree_node *old = rb_entry(rb_old, struct interval_tree_node, rb); struct interval_tree_node *new = rb_entry(rb_new, struct interval_tree_node, rb); new->__subtree_last = old->__subtree_last; old->__subtree_last = compute_subtree_last(old); } static const struct rb_augment_callbacks augment_callbacks = { augment_propagate, augment_copy, augment_rotate }; void interval_tree_insert(struct interval_tree_node *node, struct rb_root *root) { struct rb_node **link = &root->rb_node, *rb_parent = NULL; unsigned long start = node->start, last = node->last; struct interval_tree_node *parent; while (*link) { rb_parent = *link; parent = rb_entry(rb_parent, struct interval_tree_node, rb); if (parent->__subtree_last < last) parent->__subtree_last = last; if (start < parent->start) link = &parent->rb.rb_left; else link = &parent->rb.rb_right; } node->__subtree_last = last; rb_link_node(&node->rb, rb_parent, link); rb_insert_augmented(&node->rb, root, &augment_callbacks); } void interval_tree_remove(struct interval_tree_node *node, struct rb_root *root) { rb_erase_augmented(&node->rb, root, &augment_callbacks); }
5. 補充:
Interval tree上的每個節點的 key 值是一個區間,區間的起點和終點通常為整數,同一層節點所代表的區間相互不會重疊,葉子節點的區間是單位長度,不能再分了。區間樹可以參考:https://www.jianshu.com/p/e23ab4bc7dec