转载:

http://www.cnblogs.com/haippy/archive/2012/09/02/2668099.html

https://zh.wikipedia.org/zh/%E7%BA%A2%E9%BB%91%E6%A0%91

  红黑树和avl树一样,是二叉平衡搜索树,目前内核中已经找不到avl树的代码,二叉平衡搜索树都是用红黑树的接口,因此红黑树还是比较重要的。在代码实现上,红黑树的节点插入和删除较avl树复杂,主要难度是在树的旋转和node的着色,这方面中文wiki上讲的已经很清楚,可以参考上面的链接,需要静下心来看。下面的内容给出红黑树的调用实例,达到帮助读者理解红黑树接口和阅读内核源码的目的。

正文:

Linux 内核红黑树的实现代码位于:lib/rbtree.c,同时头文件在 include/linux/rbtree.h 中,内核中很多模块都使用了红黑树,详细介绍参见内核文档 Documentation/rbtree.txt。

内核中红黑树定义如下:

 struct rb_node
{
unsigned long rb_parent_color;
#define RB_RED 0
#define RB_BLACK 1
struct rb_node *rb_right;
struct rb_node *rb_left;
} __attribute__((aligned(sizeof(long)))); struct rb_root
{
struct rb_node *rb_node;
};

在这里,重点需要理解rb_parent_color这个成员,粗略一看,这里似乎没有定义颜色的域,但这就是这里红黑树实现的一个巧妙的地方。rb_parent_color这个域其实同时包含了颜色信息以及父亲节点的指针,因为该域是一个long的类型,需要大小为sizeof(long)的对齐,那么在一般的32位机器上,其后两位的数值永远是0,于是可以拿其中的一位来表示颜色。事实上,这里就是使用了最低位来表示颜色信息。明白了这点,那么以下关于父亲指针和颜色信息的操作都比较容易理解了,其本质上都是对rb_parent_color的位进行操作。

 #define rb_parent(r)   ((struct rb_node *)((r)->rb_parent_color & ~3)) //低两位清0
#define rb_color(r) ((r)->rb_parent_color & 1) //取最后一位
#define rb_is_red(r) (!rb_color(r)) //最后一位为0?
#define rb_is_black(r) rb_color(r) //最后一位为1?
#define rb_set_red(r) do { (r)->rb_parent_color &= ~1; } while (0) //最后一位置0
#define rb_set_black(r) do { (r)->rb_parent_color |= 1; } while (0) //最后一位置1 static inline void rb_set_parent(struct rb_node *rb, struct rb_node *p) //设置父亲
{
rb->rb_parent_color = (rb->rb_parent_color & ) | (unsigned long)p;
}
static inline void rb_set_color(struct rb_node *rb, int color) //设置颜色
{
rb->rb_parent_color = (rb->rb_parent_color & ~) | color;
}

然后是几个宏定义:

 #define RB_ROOT    (struct rb_root) { NULL, }                         //初始根节点指针
#define rb_entry(ptr, type, member) container_of(ptr, type, member)//包含ptr的结构体指针
#define RB_EMPTY_ROOT(root) ((root)->rb_node == NULL) //判断树是否空
#define RB_EMPTY_NODE(node) (rb_parent(node) == node) //判断节点是否空,父亲是否等于自身
#define RB_CLEAR_NODE(node) (rb_set_parent(node, node)) //设置节点为空,父亲等于自身

常用接口:

__rb_rotate_left和__rb_rotate_right就是对红黑树进行的左旋和右旋操作。注意,代码中的第一个if语句中是=而不是==,意思是先赋值,然后再对该值判断是否为空,如果不为空的情况下才设置该节点的父亲。这样代码显得非常简洁,但如果以为是==的比较,则可能会感到困惑,不够他这里也使用了两个小括号进行提示,因为一般情况只需一个括号即可。

 void __rb_rotate_left(struct rb_node *node, struct rb_root *root);
void __rb_rotate_right(struct rb_node *node, struct rb_root *root);

而rb_insert_color则是把新插入的节点进行着色,并且修正红黑树使其达到平衡。

 void rb_insert_color(struct rb_node *, struct rb_root *);

插入节点时需要把新节点指向其父亲节点,这可以通过rb_link_node函数完成:

 void rb_link_node(struct rb_node * node, struct rb_node * parent, struct rb_node ** rb_link);

删除节点则通过rb_erase进行,然后通过__rb_erase_color进行红黑树的修正。

 void rb_erase(struct rb_node *, struct rb_root *);
void __rb_erase_color(struct rb_node *node, struct rb_node *parent, struct rb_root *root);

可以通过调用rb_replace_node来替换一个节点,但是替换完成后并不会对红黑树做任何调整,所以如果新节点的值与被替换的值有所不同时,可能会出现问题。

 void rb_replace_node(struct rb_node *old, struct rb_node *new, struct rb_root *tree);

另外有几个进行红黑树遍历的函数,其原理均非常简单,本质上就是这里的求后继、前驱、最小值、最大值的函数实现,不过这里的代码实现非常简洁和巧妙。

 extern struct rb_node *rb_next(const struct rb_node *); //后继
extern struct rb_node *rb_prev(const struct rb_node *); //前驱
extern struct rb_node *rb_first(const struct rb_root *);//最小值
extern struct rb_node *rb_last(const struct rb_root *); //最大值

实际使用:

在使用内核的红黑树时,需将 struct rb_node 结构包含在自己的数据结构中,比如:

  struct mynode {
struct rb_node node;
char *string;
/*more stuff of your structure hereby*/
};

可以通过container_of宏获取包含了 rb_node 结构的起始地址,也可以通过rb_entry(node, type, member),其实:

 #define    rb_entry(ptr, type, member) container_of(ptr, type, member)

首先是搜索节点,基本思想就是根据二叉查找树的查找过程进行:

 struct mynode *my_search(struct rb_root *root, char *string)
{
struct rb_node *node = root->rb_node; while (node) {
struct mynode *data = container_of(node, struct mynode, node);
int result; result = strcmp(string, data->string); if (result < )
node = node->rb_left;
else if (result > )
node = node->rb_right;
else
return data;
}
return NULL;
}

然后是插入节点,需要在插入一个数据之前先要查找到适合插入的位置,然后将节点加入到树中并将树调整到平衡状态:

 int my_insert(struct rb_root *root, struct mynode *data)
{
struct rb_node **new = &(root->rb_node), *parent = NULL; /* Figure out where to put new node */
while (*new) {
struct mynode *this = container_of(*new, struct mynode, node);
int result = strcmp(data->string, this->string); parent = *new;
if (result < )
new = &((*new)->rb_left);
else if (result > )
new = &((*new)->rb_right);
else
return ;
} /* Add new node and rebalance tree. */
rb_link_node(&data->node, parent, new);
rb_insert_color(&data->node, root); return ;
}

释放某一节点空间:

 void my_free(struct mynode *node)
{
if (node != NULL) {
if (node->string != NULL) {
free(node->string);
node->string = NULL;
}
free(node);
node = NULL;
}
}

综合上面的代码:

 #define NUM_NODES 32

 int main()
{ struct mynode *mn[NUM_NODES]; /* *insert */
int i = ;
for (; i < NUM_NODES; i++) {
mn[i] = (struct mynode *)malloc(sizeof(struct mynode));
mn[i]->string = (char *)malloc(sizeof(char) * );
sprintf(mn[i]->string, "%d", i);
my_insert(&mytree, mn[i]);
} /* *search */
struct rb_node *node;
for (node = rb_first(&mytree); node; node = rb_next(node))
printf("key = %s\n", rb_entry(node, struct mynode, node)->string); /* *delete */
printf("delete node 20: \n");
struct mynode *data = my_search(&mytree, "");
if (data) {
rb_erase(&data->node, &mytree);
my_free(data);
} /* *delete again*/
printf("delete node 10: \n");
data = my_search(&mytree, "");
if (data) {
rb_erase(&data->node, &mytree);
my_free(data);
} /* *delete once again*/
printf("delete node 15: \n");
data = my_search(&mytree, "");
if (data) {
rb_erase(&data->node, &mytree);
my_free(data);
} /* *search again*/
printf("search again:\n");
for (node = rb_first(&mytree); node; node = rb_next(node))
printf("key = %s\n", rb_entry(node, struct mynode, node)->string);
return ;
}

linux 内核数据结构之红黑树.的更多相关文章

  1. Linux内核之于红黑树and AVL树

    为什么Linux早先使用AVL树而后来倾向于红黑树?       实际上这是由红黑树的有用主义特质导致的结果,本短文依旧是形而上的观点.红黑树能够直接由2-3树导出.我们能够不再提红黑树,而仅仅提2- ...

  2. Linux 内核里的数据结构:红黑树(rb-tree)

    转自:https://www.cnblogs.com/slgkaifa/p/6780299.html 作为一种数据结构.红黑树可谓不算朴素.由于各种宣传让它过于神奇,网上搜罗了一大堆的关于红黑树的文章 ...

  3. 物联网安全himqtt防火墙数据结构之红黑树源码分析

    物联网安全himqtt防火墙数据结构之红黑树源码分析 随着5G的发展,物联网安全显得特别重要,himqtt是首款完整源码的高性能MQTT物联网防火墙 - MQTT Application FireWa ...

  4. linux内核数据结构之链表

    linux内核数据结构之链表 1.前言 最近写代码需用到链表结构,正好公共库有关于链表的.第一眼看时,觉得有点新鲜,和我之前见到的链表结构不一样,只有前驱和后继指针,而没有数据域.后来看代码注释发现该 ...

  5. D&F学数据结构系列——红黑树

    红黑树 定义:一棵二叉查找树如果满足下面的红黑性质,则为一棵红黑树: 1)每个结点不是红的就是黑的 2)根结点是黑的 3)每个叶结点是黑的 4)如果一个结点是红的,它的两个儿子都是黑的(即不可能有两个 ...

  6. linux内核数据结构之kfifo

    1.前言 最近项目中用到一个环形缓冲区(ring buffer),代码是由linux内核的kfifo改过来的.缓冲区在文件系统中经常用到,通过缓冲区缓解cpu读写内存和读写磁盘的速度.例如一个进程A产 ...

  7. Linux 内核数据结构:Linux 双向链表

    Linux 内核提供一套双向链表的实现,你可以在 include/linux/list.h 中找到.我们以双向链表着手开始介绍 Linux 内核中的数据结构 ,因为这个是在 Linux 内核中使用最为 ...

  8. Linux 内核数据结构:双向链表

    Linux 内核提供一套双向链表的实现,你可以在 include/linux/list.h 中找到.我们以双向链表着手开始介绍 Linux 内核中的数据结构 ,因为这个是在 Linux 内核中使用最为 ...

  9. linux内核数据结构学习总结

    目录 . 进程相关数据结构 ) struct task_struct ) struct cred ) struct pid_link ) struct pid ) struct signal_stru ...

随机推荐

  1. 如何制作自己的R包

    如何制作自己的R包? 摘自 方匡南 等编著<R数据分析-方法与案例详解>.电子工业出版社 R包简介 R包提供了一个加载所需代码.数据和文件的集合.R软件自身就包含大约30种不同功能的包,这 ...

  2. shiro的Quickstart

    /** * Simple Quickstart application showing how to use Shiro's API. * * @since 0.9 RC2 */ public cla ...

  3. 作业要求20191010-3 alpha week 1/2 Scrum立会报告+燃尽图 01

    此作业要求参见:https://edu.cnblogs.com/campus/nenu/2019fall/homework/8746 一.小组情况组长:贺敬文组员:彭思雨 王志文 位军营 杨萍队名:胜 ...

  4. 【Spark机器学习速成宝典】基础篇04数据类型(Python版)

    目录 Vector LabeledPoint Matrix 使用C4.5算法生成决策树 使用CART算法生成决策树 预剪枝和后剪枝 应用:遇到连续与缺失值怎么办? 多变量决策树 Python代码(sk ...

  5. tinymq学习小结

    学了tinymq, 先将它的README翻译了一下: TinyMQ - A diminutive message queue (TinyMQ ---一个小型的消息队列) TinyMQ是一个为erlan ...

  6. 转自B站 真希望我在20岁就懂得的10个人生道理 主讲:王魄

    视频地址:https://www.bilibili.com/video/av65194244?from=search&seid=15261178568916939794 这位阿姨讲得还行,特别 ...

  7. vue-微信浏览器左上角返回按钮拦截

    [需求] 在微信公众号开发中,有时需要对浏览器左上角返回按钮进行拦截处理相关的页面逻辑,而并不是让页面直接返回上一页,之前在这个细节点上的一直实现得不是很好.但看到京东购物公众号上的效果却实现得非常好 ...

  8. linux新建用户tab无法补全命令

    查看passwd cat /ect/passwd 发现root用户的shell是/bin/bash 普通用户的shell是/bin/sh 修改普通用户的为/bin/bash即可

  9. Kettle使用教程之数据同步

    数据模型原型如下: 1.表输入,针对最新的数据输入的表 2.目标表,需要更新的表 3.两个表都需要进行排序操作 4.合并,根据id进行合并 5.数据同步(包括更新.插入.删除) 6.点击运行,就可以实 ...

  10. Keepalived + LVS-NAT 实现高可用四层 TCP/UDP 负载均衡器

    目录 文章目录 目录 前文列表 在 LVS1/2 安装 Keepalived & LVS Keepalived + LVS-NAT 实现 TCP 负载均衡 IP 规划 网络架构参考 LVS1 ...