aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/Documentation/translations/zh_CN/core-api/rbtree.rst
blob: a3e1555cb974d8949dece3678888c7fd8245635e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/core-api/rbtree.rst

:翻译:

  唐艺舟 Tang Yizhou <tangyeechou@gmail.com>

=========================
Linux中的红黑树(rbtree)
=========================


:日期: 2007年1月18日
:作者: Rob Landley <rob@landley.net>

何为红黑树,它们有什么用?
--------------------------

红黑树是一种自平衡二叉搜索树,被用来存储可排序的键/值数据对。这与基数树(被用来高效
存储稀疏数组,因此使用长整型下标来插入/访问/删除结点)和哈希表(没有保持排序因而无法
容易地按序遍历,同时必须调节其大小和哈希函数,然而红黑树可以优雅地伸缩以便存储任意
数量的键)不同。

红黑树和AVL树类似,但在插入和删除时提供了更快的实时有界的最坏情况性能(分别最多两次
旋转和三次旋转,来平衡树),查询时间轻微变慢(但时间复杂度仍然是O(log n))。

引用Linux每周新闻(Linux Weekly News):

    内核中有多处红黑树的使用案例。最后期限调度器和完全公平排队(CFQ)I/O调度器利用
    红黑树跟踪请求;数据包CD/DVD驱动程序也是如此。高精度时钟代码使用一颗红黑树组织
    未完成的定时器请求。ext3文件系统用红黑树跟踪目录项。虚拟内存区域(VMAs)、epoll
    文件描述符、密码学密钥和在“分层令牌桶”调度器中的网络数据包都由红黑树跟踪。

本文档涵盖了对Linux红黑树实现的使用方法。更多关于红黑树的性质和实现的信息,参见:

  Linux每周新闻关于红黑树的文章
    https://lwn.net/Articles/184495/

  维基百科红黑树词条
    https://en.wikipedia.org/wiki/Red-black_tree

红黑树的Linux实现
-----------------

Linux的红黑树实现在文件“lib/rbtree.c”中。要使用它,需要“#include <linux/rbtree.h>”。

Linux的红黑树实现对速度进行了优化,因此比传统的实现少一个间接层(有更好的缓存局部性)。
每个rb_node结构体的实例嵌入在它管理的数据结构中,因此不需要靠指针来分离rb_node和它
管理的数据结构。用户应该编写他们自己的树搜索和插入函数,来调用已提供的红黑树函数,
而不是使用一个比较回调函数指针。加锁代码也留给红黑树的用户编写。

创建一颗红黑树
--------------

红黑树中的数据结点是包含rb_node结构体成员的结构体::

  struct mytype {
  	struct rb_node node;
  	char *keystring;
  };

当处理一个指向内嵌rb_node结构体的指针时,包住rb_node的结构体可用标准的container_of()
宏访问。此外,个体成员可直接用rb_entry(node, type, member)访问。

每颗红黑树的根是一个rb_root数据结构,它由以下方式初始化为空:

  struct rb_root mytree = RB_ROOT;

在一颗红黑树中搜索值
--------------------

为你的树写一个搜索函数是相当简单的:从树根开始,比较每个值,然后根据需要继续前往左边或
右边的分支。

示例::

  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;
  }

在一颗红黑树中插入数据
----------------------

在树中插入数据的步骤包括:首先搜索插入新结点的位置,然后插入结点并对树再平衡
("recoloring")。

插入的搜索和上文的搜索不同,它要找到嫁接新结点的位置。新结点也需要一个指向它的父节点
的链接,以达到再平衡的目的。

示例::

  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) {
  		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;
  }

在一颗红黑树中删除或替换已经存在的数据
--------------------------------------

若要从树中删除一个已经存在的结点,调用::

  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);

通过这种方式替换结点不会对树做重排序:如果新结点的键值和旧结点不同,红黑树可能被
破坏。

(按排序的顺序)遍历存储在红黑树中的元素
----------------------------------------

我们提供了四个函数,用于以排序的方式遍历一颗红黑树的内容。这些函数可以在任意红黑树
上工作,并且不需要被修改或包装(除非加锁的目的)::

  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结构体的指针,由此,包住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);

带缓存的红黑树
--------------

计算最左边(最小的)结点是二叉搜索树的一个相当常见的任务,例如用于遍历,或用户根据
他们自己的逻辑依赖一个特定的顺序。为此,用户可以使用'struct rb_root_cached'来优化
时间复杂度为O(logN)的rb_first()的调用,以简单地获取指针,避免了潜在的昂贵的树迭代。
维护操作的额外运行时间开销可忽略,不过内存占用较大。

和rb_root结构体类似,带缓存的红黑树由以下方式初始化为空::

  struct rb_root_cached mytree = RB_ROOT_CACHED;

带缓存的红黑树只是一个常规的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 *);


对增强型红黑树的支持
--------------------

增强型红黑树是一种在每个结点里存储了“一些”附加数据的红黑树,其中结点N的附加数据
必须是以N为根的子树中所有结点的内容的函数。它是建立在红黑树基础设施之上的可选特性。
想要使用这个特性的红黑树用户,插入和删除结点时必须调用增强型接口并提供增强型回调函数。

实现增强型红黑树操作的C文件必须包含<linux/rbtree_augmented.h>而不是<linux/rbtree.h>。
注意,linux/rbtree_augmented.h暴露了一些红黑树实现的细节而你不应依赖它们,请坚持
使用文档记录的API,并且不要在头文件中包含<linux/rbtree_augmented.h>,以最小化你的
用户意外地依赖这些实现细节的可能。

插入时,用户必须更新通往被插入节点的路径上的增强信息,然后像往常一样调用rb_link_node(),
然后是rb_augment_inserted()而不是平时的rb_insert_color()调用。如果
rb_augment_inserted()再平衡了红黑树,它将回调至一个用户提供的函数来更新受影响的
子树上的增强信息。

删除一个结点时,用户必须调用rb_erase_augmented()而不是rb_erase()。
rb_erase_augmented()回调至一个用户提供的函数来更新受影响的子树上的增强信息。

在两种情况下,回调都是通过rb_augment_callbacks结构体提供的。必须定义3个回调:

- 一个传播回调,它更新一个给定结点和它的祖先们的增强数据,直到一个给定的停止点
  (如果是NULL,将更新一路更新到树根)。

- 一个复制回调,它将一颗给定子树的增强数据复制到一个新指定的子树树根。

- 一个树旋转回调,它将一颗给定的子树的增强值复制到新指定的子树树根上,并重新计算
  先前的子树树根的增强值。

rb_erase_augmented()编译后的代码可能会内联传播、复制回调,这将导致函数体积更大,
因此每个增强型红黑树的用户应该只有一个rb_erase_augmented()的调用点,以限制编译后
的代码大小。


使用示例
^^^^^^^^

区间树是增强型红黑树的一个例子。参考Cormen,Leiserson,Rivest和Stein写的
《算法导论》。区间树的更多细节:

经典的红黑树只有一个键,它不能直接用来存储像[lo:hi]这样的区间范围,也不能快速查找
与新的lo:hi重叠的部分,或者查找是否有与新的lo:hi完全匹配的部分。

然而,红黑树可以被增强,以一种结构化的方式来存储这种区间范围,从而使高效的查找和
精确匹配成为可能。

这个存储在每个节点中的“额外信息”是其所有后代结点中的最大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) {
				/*
				 * Some nodes in left subtree satisfy Cond2.
				 * Iterate to find the leftmost such node N.
				 * If it also satisfies Cond1, that's the match
				 * we are looking for. Otherwise, there is no
				 * matching interval as nodes to the right of N
				 * can't satisfy Cond1 either.
				 */
				node = left;
				continue;
			}
		}
		if (node->start <= last) {		/* Cond1 */
			if (node->last >= start)	/* Cond2 */
				return node;	/* node is leftmost match */
			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);
  }