详解设计红黑树 - 3

170 阅读2分钟

这是我参与8月更文挑战的第13天,活动详情查看:8月更文挑战

接口

nginx

/* macros */
#define rbtree_set_insert_func(tree, func) (tree)->insert = func

#define rbtree_red(node) ((node)->color = 1)
#define rbtree_black(node) ((node)->color = 0)
#define rbtree_is_red(node) ((node)->color)
#define rbtree_is_black(node) (!((node)->color))

// copy color of n2 to n1
#define rbtree_copy_color(n1, n2) (n1->color = n2->color)
// sentinel for black leaf node
#define rbtree_sentinel_init(node) rbtree_black(node)
// public methods
void rbtree_init(rbtree_t *tree, rbtree_node_t *s);
void rbtree_insert_value(rbtree_t *tree, rbtree_node_t *node);
void rbtree_insert(rbtree_t *tree, rbtree_node_t *node);
void rbtree_delete(rbtree_t *tree, rbtree_node_t *node);
rbtree_node_t *rbtree_next(rbtree_t *tree, int key);

static inline rbtree_node_t *rbtree_get_min(rbtree_t *tree, rbtree_node_t

#endif

我们可以参照学习一下,nginx提供以上公共接口。 其中宏定义用来进行逻辑判定和将赋值函数化。

nginx源码中,root经常使用双重结点指针,也就是根结点地址的地址。如果树的修改过程中,根结点地址被别的结点地址替换掉,需要重新设置根的地址root。假设ngx_rbtree_t中的根地址参数是root单层指针,进入函数体时将是一个值传递,出函数体时无论函数体中如何更改根的地址,都是无效的,只有对根结点内容的修改能保留下来。所以要么使用双重指针作为根地址的参数,要么提供树结构体的地址,变相提供双重指针作为参数,当然可以提供树的结构体对象本身作为参数,但是值传递是要复制整个值对象的,显然当结构体比较大时这样做将明显增加开销。nginx选择双重指针而非结构体指针来避免树结构体内的变量遍历寻址,进一步提高效率。

linux

Linux 内核提供了一套完整的红黑树结构,便于开发者在自己的程序中使用红黑树, Linux 使用 struct rb_node 结构定义了一棵红黑树的根节点,并且 struct rb_node 结构一般内嵌在更大的数据结构之中。内核并未直接提供查找相关的函数,开发者只能 更具实际情况进行编写,红黑树是标准的二叉树,可以按前序、中序、后序、或者 层序进行节点的查找,参考如下:
红黑树内核接口函数列表
__rb_change_child

RB_CLEAR_NODE

__rb_color

rb_color

RB_EMPTY_NODE

RB_EMPTY_ROOT

rb_entry

rb_entry_safe

rb_erase

rb_first

rb_first_postorder

rb_insert_color

__rb_is_black

rb_is_black

__rb_is_red

rb_is_red

rb_last

rb_link_node

rb_next

rb_next_postorder

rb_parent

rb_prev

rb_replace_node

RB_ROOT

rb_set_parent

rb_set_parent_color

rbtree_postorder_for_each_entry_safe

可以看出, Linux 提供的函数接口较 nginx 更多。

测试

nginx image.png change_replace

image.png

change insert_rebalance

image.png