一、二叉树

  • 一个二叉树是每个节点最多只有两个出边的数
  • 二叉树的内容不是本文重点,不再叙述

二、二叉搜索树

  • 一个二叉搜索树(简称为“BST”)是一个节点有序的二叉树,其顺序通常遵循下列法则:
    • 1.根的左分支节点值都小于根节点值
    • 2.右分支节点值都大于根节点值
    • 3.所有的子树也都是二叉搜索树
  • 因此,一个二叉搜索树所有节点必然都有序,且左子节点小于其父节点值,而右子节点大于其父节点值的二叉树。所以,在树中搜索一个给定值或者按序遍历树都相当快捷(算法分别是对数和线性的)
  • 下图是一个简单的二叉搜索树

Linux(内核剖析):17---内核数据结构之二叉树(struct rb_root、struct rb_node)

三、平衡二叉搜索树

  • 一个平衡二叉搜索树是一个所有叶子节点深度差不超过1的二叉搜索树(如下图所示)
    • 节点的深度:是指从根节点起,到达它一共需要经过的父节点数目

Linux(内核剖析):17---内核数据结构之二叉树(struct rb_root、struct rb_node)

四、自平衡二叉搜索树

  • 一个自平衡二叉搜索树是指其操作都试图维持(半)平衡的二叉搜索树

五、红黑树

  • 红黑树是一种自平衡二叉搜索树
  • Linux主要的平衡二叉树数据结构就是红黑树。红黑树具有特殊的着色属性,或红色或黑色
  • 红黑树因遵循下面六个属性,所以能维持半平衡结构:
    • 1.所有的节点要么着红色,要么着黑色
    • 2.叶子节点都是黑色
    • 3.叶子节点不包含数据
    • 4.所有非叶子结点都有两个子节点
    • 5.如果一个节点时红色,则它的子节点都是黑色
    • 6.在一个节点到期叶子节点的路径中,如果总是包含同样数目的黑色节点,则该路径相比其他路径是最短的

红黑树为什么是半平衡的?

上述条件,保证了最深的叶子节点的深度不会大于两倍的最浅叶子节点的深度。所以,红黑树总是半平衡的

  • 首先,第五个属性,一个红节点不能是其他红色节点的子节点或者父节点
  • 第六个属性保证了,从树的任何节点到其叶子节点的路径都具有相同数目的黑色节点,树里的最长路径则是红黑交替节点路径,所以最短路径必然是具有相同数量黑色节点的——只包含黑色节点的路径
  • 于是从根节点到叶子节点的的最长路径不会超过最短路径的两倍

六、Linux内核实现的红黑树(rbtree)

 

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

根节点(rb_root、RB_ROOT)

  • rbtree的根节点由数据结构rb_root描述(代码来自Linux 2.6.22/include/linux/retree.h)
struct rb_root
{
	struct rb_node *rb_node;
};
  • 根节点的初始化:创建一个红黑树,我们需要分配一个新的rb_root结构,并且需要初始化为特殊值RB_ROOT
struct rb_root root = RB_ROOT;
  • RB_ROOT定义如下(代码来自Linux 2.6.22/include/linux/retree.h) 
#define RB_ROOT	(struct rb_root) { NULL, }

树的节点(rb_node)

  • 树的其它节点由结构rb_node描述(代码来自Linux 2.6.22/include/linux/retree.h)
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))));
  • rbtree的一些特点:

    • rbtree的实现并没有提供搜索和插入例程,这些例程希望由rbtree的用户自己定义。这是因为C语言不大容易进行泛型编程,同时Linux内核开发者们相信最有效的搜索和插入方法需要每个用户自己去实现
    • 你可以使用rbtree提供的辅助函数,但你自己要实现比较操作算子

搜索演示案例

  • 因为Linux内核没有实现rbtree的搜索和插入例程,所以我们以演示案例来展示Linux内核树的使用
  • 下面的函数实现了在页高速缓存搜索一个文件区(由一个i节点和一个偏移量共同描述)。每个i节点都有自己的rbtre,以关联在文件中的页偏移。该函数将搜索给定i节点的rbtree,以寻找匹配的偏移值

Linux(内核剖析):17---内核数据结构之二叉树(struct rb_root、struct rb_node)

  • 函数解析:

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

插入演示案例

  • 插入操作相对复杂一些,因为必须实现搜索和插入逻辑。下面并非一个了不起的函数,但可以作为你实现自己的插入操作的一个指导
  • 函数解析:

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

Linux(内核剖析):17---内核数据结构之二叉树(struct rb_root、struct rb_node)