Chinaunix首页 | 论坛 | 博客
  • 博客访问: 900026
  • 博文数量: 119
  • 博客积分: 2493
  • 博客等级: 大尉
  • 技术积分: 2363
  • 用 户 组: 普通用户
  • 注册时间: 2012-06-03 14:00
文章分类

全部博文(119)

文章存档

2013年(19)

2012年(100)

分类: LINUX

2012-08-07 17:37:52

         红黑树是平衡树的一种,在查找,插入,删除方面的性能都是
O(log(n)),所以应用也很广泛,比如说在linux内核中,内存管理中的vm_area_struct的组织就有一种红黑树的组织,还有普通进程的CFS调
度,该CFS运行队列就是一棵红黑树,可以很快找到要运行哪个进程。
-------------------------------------------------------------------------------------------
以下代码多数来自于2.6内核的include/linux/rbtree.h和/lib/rbtree.c文件。
 红黑树节点的定义:

  1. struct rb_node
  2. {
  3. unsigned long rb_parent_color;
  4. #define RB_RED 0
  5. #define RB_BLACK 1
  6. struct rb_node *rb_right;
  7. struct rb_node *rb_left;
  8. } __attribute__((aligned(sizeof(long))));
内核中每个红黑树节点均包含5个域:
rb_right :右孩子节点指针。
rb_left  :左孩子节点指针。
rb_parent_color:该字段包含两个域,一个是parent,一个是color,
这点用的技巧就是内存对齐,此结构体的对齐系数是sizeof(long)=4,
故结构体的地址后两位均是0,故就可以使用这两位来标识该节点的颜色,
其实只需要一位即可。
数据域:内核中的一些数据结构都喜欢把数据域放在外面,
这样可以将结构封装和拓展。比如说链表,struct list_head {};也是把
数据域放在外面
--------------------------------------------------------------------------------------------
  1. #define rb_parent(r) ((struct rb_node *)((r)->rb_parent_color & ~3))
这个宏函数,就是从r这个节点中“解析”出r节点的父节点。使用的&运算,
将r节点中rb_parent_color中的前30位(后两位有一位是用来标识颜色的)
解析出来。

  1. #define rb_color(r) ((r)->rb_parent_color & 1)
该宏函数,就是从r这个节点中“解析”出r节点的颜色,只有红和黑两种,
故只需要用一位来表示即可,这儿使用的是最后一位,故只需将最后一位
&出即可。
----------------------------------------------------------------------------------------------
  1. #define rb_is_red(r) (!rb_color(r))
  2. #define rb_is_black(r) rb_color(r)
此处两个宏用来判别该节点是红节点孩是黑节点。该信息在rb_parent_color
最后一位标识出,如果最后一位是1则表示是黑节点,最后一位是0则表示是
红节点。

  1. #define rb_set_red(r) do { (r)->rb_parent_color &= ~1; } while (0)
  2. #define rb_set_black(r) do { (r)->rb_parent_color |= 1; } while (0)
该处两个宏函数,用来设置r节点为红节点还是黑节点。设置的方法就是
修改字段rb_parent_color的最后1位。
----------------------------------------------------------------------------------------------
  1. 123 static inline void rb_set_parent(struct rb_node *rb, struct rb_node *p)
  2. 124 {
  3. 125 rb->rb_parent_color = (rb->rb_parent_color & 3) | (unsigned long)p;
  4. 126 }
该函数将rb节点的父节点设置为p,设置起来也比较容易,将rb节点中的rb_parent_color字段的前30位赋值为p节点的前30位即可。
----------------------------------------------------------------------------------------------
  1. 354 /*
  2. 355 * This function returns the first node (in sort order) of the tree.
  3. 356 */
  4. 357 struct rb_node *rb_first(const struct rb_root *root)
  5. 358 {
  6. 359 struct rb_node *n;
  7. 360
  8. 361 n = root->rb_node;
  9. 362 if (!n)
  10. 363 return NULL;
  11. 364 while (n->rb_left)
  12. 365 n = n->rb_left;
  13. 366 return n;
  14. 367 }
该函数比较容易理解,就是求红黑树中最左边的一个节点,红黑树也是一
个排序树,最左节点也就是key值最小的一个节点。原理就是不断地往左往左。

  1. 370 struct rb_node *rb_last(const struct rb_root *root)
  2. 371 {
  3. 372 struct rb_node *n;
  4. 373
  5. 374 n = root->rb_node;
  6. 375 if (!n)
  7. 376 return NULL;
  8. 377 while (n->rb_right)
  9. 378 n = n->rb_right;
  10. 379 return n;
  11. 380 }
该函数与rb_first是对称的,该函数就是求红黑树最右节点,key值
最大的一个节点。不断往右往右即可。
----------------------------------------------------------------------------------------------
在红黑树的插入和删除中都要用到调整,其中调整就用到了旋转
(分左旋和右旋)。下面分析下原理。


左旋,以Y为轴,x逆时针旋转,Y的左子树交给X,作为X的右子树,
然后就是Y取代X的位置。

右旋,以X为轴,Y顺时针旋转,X的右子树交给Y,作为Y的左子树,
然后就是X取代Y的位置。
----------------------------------------------------------------------------------------------
左旋的代码实现:
  1. static void __rb_rotate_left(struct rb_node *node, struct rb_root *root)
  2. {   //node为要旋转的节点,就是下图中的X节点。
  3.     struct rb_node *right = node->rb_right;//由node节点获取右节点
  4.     struct rb_node *parent = rb_parent(node);//由node节点获取node节点的父节点,即图中的parent

  5.     if ((node->rb_right = right->rb_left))//将right节点的左子树交给node节点,并判断左子树是否
  6.         rb_set_parent(right->rb_left, node);//为空,注意这儿是赋值操作,不是判断相等。
  7.        //然后把node设置为right->left的父节点。这地方有点“双向链表”的意思。父指向子,子也指向父。
  8.     right->rb_left = node;//将node这整个子树作为right的左子树,这样就实现了旋转。
  9. //--------------------------------------------------------------------------------------
  10.     rb_set_parent(right, parent);//将right的父指针指向parent。取代node的位置

  11.     if (parent)//如果X不是根节点
  12.     {
  13.         if (node == parent->rb_left)//如果node原来是parent的左孩子
  14.             parent->rb_left = right;//则将right设置为parent的左孩子。
  15.         else
  16.             parent->rb_right = right;//否则将right设置为parent的右孩子。
  17.     }
  18.     else //如果parent指针为空,说明X是根节点。
  19.         root->rb_node = right;//然后就把right设置为根节点。
  20. //----------------------------------------------------------------------------------------
  21.     rb_set_parent(node, right);//让node的父指针指向right。
  22. }


上面的没一次建立两个节点之间的连接都是双向的。比如要建立X-B之间的
连接,建立X-Y之间的连接,建立Y-P之间的连接。将其看做链表的话,都
是双向链表。

建立X-B之间的连接:
if ((node->rb_right = right->rb_left))
      rb_set_parent(right->rb_left, node); 
这个赋值操作node->rb_right = right->rb_left就建立了一次,如果左子树不为空,则再建立一次 
rb_set_parent(right->rb_left, node)

建立X-Y之间的连接:
right->rb_left = node; 
rb_set_parent(node, right);

建立Y-P之间的连接:
rb_set_parent(right, parent);
parent->rb_left = right;(或:parent->rb_right = right;或:root->rb_node = right;
------------------------------------------------------------------------------------
右旋,和左旋是对称的,所有的思想和代码和左旋都差不多,所以不再详
细分析了。

  1. static void __rb_rotate_right(struct rb_node *node, struct rb_root *root)
  2. {
  3.     struct rb_node *left = node->rb_left;
  4.     struct rb_node *parent = rb_parent(node);

  5.     if ((node->rb_left = left->rb_right))
  6.         rb_set_parent(left->rb_right, node);
  7.     left->rb_right = node;

  8.     rb_set_parent(left, parent);

  9.     if (parent)
  10.     {
  11.         if (node == parent->rb_right)
  12.             parent->rb_right = left;
  13.         else
  14.             parent->rb_left = left;
  15.     }
  16.     else
  17.         root->rb_node = left;
  18.     rb_set_parent(node, left);
  19. }
--------------------------------------------------------------------------------------
参考:http://wangcong.org/blog/archives/12
阅读(1116) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

zhe_wang2012-08-09 15:29:05