第6章内核数据结构之二叉树

6.4 二叉树

树结构是一个能提供分层的树型数据结构的特定数据结构。在数学意义上,树是一个无环的、连接的有向图,其中任何一个顶点具有0个或者多个出边以及0个或者1个入边。一个二叉树是每个节点最多只有两个出边的树——也就是,一个树,其节点具有0个、1个或者2个子节点。如图6-6所示的简单二叉树:

6.4.1 二叉搜索树

一个二叉搜索树(通常简称为BST)是一个节点有序的二叉树,其顺序通常遵循如下法则:

根的左分支节点值都小于根节点值。

右分支节点值都大于根节点值。

所有的子树也都是二叉搜索树。

因此,一个二叉搜索树所有节点必然都有序,且左子节点小于其父节点值,而右子节点大于其父节点值的二叉树。所以,在树中搜索一个给定值或者按序遍历树相当快捷。图6-7给出的简单二叉搜索树。

6.4.2 自平衡二叉搜索树

一个节点的深度是指从其根节点起,到达它一共需经过的父节点数目。处于树底层的节点(没有子节点)称为叶子节点。一个树的高度是指树中的处于最底层节点的深度。一个平衡二叉搜索树是一个所有叶子节点深度差不超过1的二叉搜索树。如图6-8:

1、红黑树

红黑树是一种自平衡二叉搜索树。Linux主要的平衡二叉树数据结构就是红黑树。红黑树具有特殊的着色属性,或红色或黑色。红黑树因遵循下面六个属性,所以能维持半平衡结构:

1)所有的节点要么着红色,要么着黑色。

2)叶子节点都是黑色。

3)叶子节点不包含数据。

4)所有非叶子节点都有两个子节点。

5)如果一个节点是红色,则它的子节点都是黑色。

6)在一个节点到其叶子节点的路径中,如果总是包含同样数目的黑色节点,则该路径相比其他路径是最短的。

上述条件,保证了最深的叶子节点的深度不会大于两倍的最浅叶子节点的深度。所以,红黑树总是半平衡的。为什么具有如此神奇的特点?首先,第五个属性,一个红色节点不能是其他红色节点的子节点或者父节点。而第六个属性保证了,从树的任何节点到其叶子节点的路径都具有相同数目的黑色节点,树里的最长路径则是红黑交替节点路径,所以最短路径必然是具有相同数量黑色节点的——只包含黑色节点的路径。于是从根节点到叶子节点的最长路径不会超过最短路径的两倍。

如果插入和删除操作可以遵循上述六个要求,那这个树会始终保持是一个半平衡树。为什么插入和删除动作都需要服从这些特别的约束,为什么不能用一些简单的规则去维持平衡树呢?其实,实践证明这些规则遵循起来还是相对简单的。而且在保证半平衡树前提下,这些插入和删除动作并不会增加额外负担。

2、rbtree

Linux实现的红黑树称为rbtree。其定义在文件lib/rbtree.c中,声明在文件linux/rbtree.h中。除了一定的优化外,Linux的rbtree类似于之前所描述的经典红黑树,即保持了平衡性,所以插入效率和树中节点数目呈对数关系。

rbtree的根节点由数据结构rb_root描述。创建一个红黑树,要分配一个新的rb_root结构,并且需要初始化为特殊值RB_ROOT:

struct rb_root root = RB_ROOT;

树里的其他节点由结构rb_node描述。给定一个rb_node,可以通过跟踪同名节点指针来找到它的左右子节点。

rbtree的实现并没有提供搜索和插入例程,这些例程希望由rbtree的用户自己定义。搜索操作和插入操作最好的范例就是展示一个实际场景:先来看搜索,下面的函数实现了在页高速缓存中搜索一个文件区。每个i节点都有自己的rbtree,以关联在文件中的页偏移。该函数将搜索给定i节点的rbtree,以寻找匹配的偏移值:

这个例子中,在while循环中遍历了整个rbtree。offset决定是向左或是向右遍历。if和else条件实际上实现了rbtree的比较方法,从而确保了树的有序性。如果循环中找到了一个匹配offset的节点,则搜索完成,并返回对应的page结构。如果循环查找了全树也没有找到一个匹配项,说明在树中不存在匹配项,则函数返回NULL。

插入操作要相对复杂一些,因为必须实现搜索和插入逻辑。

和搜索操作一样,while循环需要遍历整个树,也是根据offset选择遍历方向。但是和搜索不同的是,该函数希望找不到匹配的offset,因为它想要找的是新offset要插入的叶子节点。当插入点找到后,调用rb_link_node()在给定位置插入新节点。接着调用rb_insert_color()方法执行复杂的再平衡动作。如果页被加入到页高速缓存中,则返回NULL。如果页已经在高速缓存中了,则返回这个已存在的页结构地址。

 

猜你喜欢

转载自blog.csdn.net/xiezhi123456/article/details/81774264