linux-rbtree

红黑树原理

红黑树是一种有序的平衡二叉树,5个性质:
1、 每个结点的颜色只能是红色或黑色。
2、 根结点是黑色的。
3、 每个叶子结点都带有两个空的黑色结点(被称为黑哨兵),如果一个结点n只有一个左孩子,那么n的右孩子是一个黑哨兵;如果结点n只有一个右孩子,那么n的左孩子是一个黑哨兵。
4、 如果一个结点是红的,则它的两个儿子都是黑的。也就是说在一条路径上不能出现相邻的两个红色结点。果然富不过二代,如果一个结点是黑的,那它是可以有黑儿子的。
5、 对于每个结点来说,从该结点到其子孙叶结点的所有路径上包含相同数目的黑结点。
性质4和5保证了,在一棵二叉查找树上,执行查找、插入、删除等操作的时间复杂度为O(lgn)。

红黑树在Linux内核中的应用

一 主要数据结构

include/linux/rbtree.h和include/linux/rbtree_augmented.h
1 rb_node
struct rb_node {
	unsigned long  __rb_parent_color;
	struct rb_node *rb_right;
	struct rb_node *rb_left;
} __attribute__((aligned(sizeof(long))));
    /* The alignment might seem pointless, but allegedly CRIS needs it */

struct rb_root {
	struct rb_node *rb_node;
};
(1) rb_node就是一个红黑树结点,有意思的是__rb_parent_color成员,这里存储了自己的爸爸和自己的颜色;为什么可以这样用呢?这要感谢aligned(sizeof(long)啊,long对齐,对于32bit系统,rb_node结构的低2bit肯定为0;爸爸的胸怀是很宽广的,于是收了这个颜色放到自己的尾巴上;r、b两种颜色,只用了最后1bit,0表示红色,1表示黑色。
#define	RB_RED		0
#define	RB_BLACK	1

几个相关的宏定义:

//取出parent的指针,将__rb_parent_color的低2bit清0
#define rb_parent(r)   ((struct rb_node *)((r)->__rb_parent_color & ~3))
//取出颜色
#define __rb_color(pc)     ((pc) & 1)
#define rb_color(rb)       __rb_color((rb)->__rb_parent_color)
(2) struct rb_root就是rb_node的指针,表示一个rbtree的根。据说这样定义,可以避免二级指针的麻烦。
2 rb_augment_callbacks
struct rb_augment_callbacks {
	void (*propagate)(struct rb_node *node, struct rb_node *stop);
	void (*copy)(struct rb_node *old, struct rb_node *new);
	void (*rotate)(struct rb_node *old, struct rb_node *new);
};
这个结构中的回调函数是给扩展红黑树用的,扩展红黑树也另有一套操作接口。

二 主要函数

1 初始化,读写自己的爸爸和自己的颜色,以及判断一个结点的颜色都比较简单。
2 rb_first(),已知树根,查找first node最小或最大的结点,就是最左边的叶子结点。
3 rb_last(),已知树根,查找last node最大或最小,就是最右边的叶子结点。
4 rb_next(),已知一个结点,查找next node后继结点,这要分几种情况:
(1) 一棵空树,只有一个结点,自己是自己的parent,直接return NULL。
(2) 右子结点不为NULL,先找到它的右结点,然后在右子树中一路左下去;红黑树的有序,是中序遍历;所以next要一路左下去。
(3) 假设树是从小到大的排列顺序,没有右孩子,就要找到自己的父亲;如果自己是个右孩子,那自己一直是最大的;只有自己为左孩子的时候,其父亲和右哥哥才是比自己大的;按照这个原理,一直找。如果找到自己是个左孩子了,父亲就是next了;如果找到根了,根的父亲是NULL啊,最后return parent就是个NULL。
5 rb_prev(),已知一个结点,查找prev node,就是先找到它的左结点,然后一路右下去。
6 rb_replace_node(),已知树根,用new node代替victim node。
7 rb_link_node(),插入一个node。
static inline void rb_link_node(struct rb_node * node, struct rb_node * parent,
				struct rb_node ** rb_link)
{
	node->__rb_parent_color = (unsigned long)parent;
	node->rb_left = node->rb_right = NULL;

	*rb_link = node;
}
先找到node的parent,一般需要迭代查找;然后初始化左右孩子为NULL,看来想让它做叶子;最后保存到rb_link中,其实是赋给自己的父亲的孩子。
8 rb_insert_color(),插入后的平衡调整。
void rb_insert_color(struct rb_node *node, struct rb_root *root)
{
	__rb_insert(node, root, dummy_rotate);
}
新插入的结点都设置为叶子结点,并染成红色,为什么染成红色?第5条说了,从某一结点到子孙结点所有路径上的黑结点是相同的,所以我们插入黑结点,避免破坏平衡;不破坏第5条,还可能破坏其他几条;那就需要通过rb_insert_color()的颜色调整和左右旋转来恢复平衡。该函数和函数7一般配合使用;7确定了一个node的parent后,8中把它插入以root为根的红黑树中。插入操作也需要一个循环迭代,但是总的旋转次数不会超过两次。
9 rb_erase(),删除一个node。
void rb_erase(struct rb_node *node, struct rb_root *root)
{
	struct rb_node *rebalance;
	rebalance = __rb_erase_augmented(node, root, &dummy_callbacks);
	if (rebalance)
		____rb_erase_color(rebalance, root, dummy_rotate);
}
首先,将node脱离以root为根的红黑树;然后,根据实际情况确定是否需要平衡调整。

三 应用实例

hrtimer里用到了定时器,但是把rb_node封装了一下,先了解两个结构。
struct timerqueue_node {
	struct rb_node node;
	ktime_t expires;
};

struct timerqueue_head {
	struct rb_root head;
	struct timerqueue_node *next;
};
就把timerqueue_head当根,timerqueue_node当结点就好。
1 初始化树根root
static inline void timerqueue_init_head(struct timerqueue_head *head)
{
	head->head = RB_ROOT;
	head->next = NULL;
}
#define RB_ROOT	(struct rb_root) { NULL, }
2 初始化结点
static inline void timerqueue_init(struct timerqueue_node *node)
{
	RB_CLEAR_NODE(&node->node);
}
#define RB_CLEAR_NODE(node)  \
	((node)->__rb_parent_color = (unsigned long)(node))
3 添加一个结点
void timerqueue_add(struct timerqueue_head *head, struct timerqueue_node *node)
{
	struct rb_node **p = &head->head.rb_node;
	struct rb_node *parent = NULL;
	struct timerqueue_node  *ptr;

	/* Make sure we don't add nodes that are already added */
	WARN_ON_ONCE(!RB_EMPTY_NODE(&node->node));

	while (*p) {
		parent = *p;
		ptr = rb_entry(parent, struct timerqueue_node, node);
		if (node->expires.tv64 < ptr->expires.tv64)
			p = &(*p)->rb_left;
		else
			p = &(*p)->rb_right;
	}
	rb_link_node(&node->node, parent, p);
	rb_insert_color(&node->node, &head->head);

	if (!head->next || node->expires.tv64 < head->next->expires.tv64)
		head->next = node;
}

(1) 如果node是刚刚初始化没有add过的,!RB_EMPTY_NODE(&node->node)为0;就不会有警告输出了。
(2) 找到爸爸,插入结点。由于hrtimer要求按照到期时间从小到大的顺序排序,所以我们从根root开始找,如果node的到期时间小,就在左子树中继续;如果node的到期时间大,就在右子树中继续;循环迭代,直到找到一个叶子结点结束,此时parent已经找到,它是一个叶子结点。
(3) rb_link_node()把node插入到树中,貌似插入的都是叶子结点。为什么这样就是插入了呢?传入的参数p中存的是2中找到的parent的一个孩子的二级指针,parent是个叶子结点,它的孩子基本就是个黑哨兵了,rb_link_node()中会*rb_link = node,这是让parent不再指向黑哨兵,而是指向node。
(4) rb_insert_color()是插入后做平衡调整。
(5) head->next = node;是因为hrtimer会用timerqueue_head->next存储最快到期的timerqueue_node,所以需要根据到期时间更新next。

4 删除一个结点
void timerqueue_del(struct timerqueue_head *head, struct timerqueue_node *node)
{
	WARN_ON_ONCE(RB_EMPTY_NODE(&node->node));

	/* update next pointer */
	if (head->next == node) {
		struct rb_node *rbn = rb_next(&node->node);

		head->next = rbn ?
			rb_entry(rbn, struct timerqueue_node, node) : NULL;
	}
	rb_erase(&node->node, &head->head);
	RB_CLEAR_NODE(&node->node);
}
(1) 是不是空树,是就给个警告。
(2) timerqueue_head->next存的是最快到期的timerqueue_node,如果需要删除的就是它,那需要更新一下next。找到node的下一个,自然成为最后到期的timerqueue_node。
(3) 用rb_erase()删除,其实就是把它从红黑树中脱离;然后进行平衡调整。
(4) 初始化被删除的结点,就是把自己赋给自己的爸爸。
struct timerqueue_node *timerqueue_iterate_next(struct timerqueue_node *node)
{
	struct rb_node *next;

	if (!node)
		return NULL;
	next = rb_next(&node->node);
	if (!next)
		return NULL;
	return container_of(next, struct timerqueue_node, node);
}
利用rb_next()找,分前面说过的三种情况。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值