跳转至

2. Red Black Tree & B+ Tree

约 3363 个字 预计阅读时间 17 分钟

2.1 Red Black Tree

红黑树是满足如下性质的一种二叉搜索树:

  1. Every node is either red or black.

  2. The root is black.

  3. Every leaf (NIL) is black.

  4. if a node is red, then both its children are black.

  5. For each node, all simple paths from the node to descendant leaves contain the same number of black nodes.

对于每个结点,从该结点到后代叶子的所有简单路径都包含相同数量的黑色结点。

注意点:

1. 红结点的父结点一定为黑结点

2. 性质3,所有的NULL leaf 都是black,你看到的leaf不是NULL,但是它的左右结点是NULL,未显示的左右儿子也是黑结点。

此处叶子结点被重新定义,称所有两个子结点都是NULL的结点为末端结点(也就是传统意义上的leaf)

3. 由性质5得,合法红黑树的红色结点的两个子结点(存在且为黑色)一定都是叶子或者都不是叶子,不然从红结点出发,到两个子结点,包含不同数量的黑色结点

4. 我们称NIL 为外部结点(external),其余有键值的结点为内部结点(internal)。

img

上图的红黑树是否合法?

16 号结点的右儿子是一个黑叶子(NULL),而这个叶子到根的路径上只有 3 个黑结点,而其他叶子到根都有 4 个黑结点。

black height的定义:

特定结点的黑高,等于该结点到叶结点(NIL)到简单路径中(不包括自身),黑色结点的数量。记作\(bh(X)\),其中整棵红黑树的黑高也就是根结点的黑高

【lemma】一个有 \(N\) 个内部结点(不包括叶子结点)的红黑树,其高度最大为 \(2log_⁡2(N + 1)\)

【lemma】\(bh(Tree) >= h(Tree)/2\) 黑结点的数量至少占树高的一半

image-20240304104841849

  1. 红黑树结点最少情况,全是黑结点,此时就是二分搜索树,二分搜索树\(sizeof(x) = 2^{h(x)} - 1\)请注意此处为内部结点 大于是因为此时的二分搜索树并不一定是完全平衡二叉树
  2. \(bh(child) = bh(x)\ or \ bh(x)-1\), 分别对应child是否为黑结点
  3. \(sizeof(x) = 1 + 2 \times sizeof(child) >= 1 + 2 \times (2^{bh(child)}-1) >= 2 \times 2^{bh(x)-1} -1 = 2^{bh(x)}-1\)
  4. 红结点的孩子一定是黑结点,所以有红必有黑任何路径上黑色结点必定占到至少一半的数量

2.1.1 Insert

前情提要:如果插入一个红色结点,也就是一个NULL结点被红色结点替代,此时并不会改变黑高。但是需要满足红色结点互不相邻的性质。

如果插入的是黑色结点,那么性质5一定会被打破,黑高一定会加1

总体思路为:先将红黑树当作普通的二叉搜索树,将目标数据插入到书中的末端并将它染成红色,再调整使之在满足黑高不变的情况下,红色结点不相邻

不妨先假设我们插入的是红色结点。

  • 如果插在黑色结点下面,没有影响

  • 如果插入空树,则将红结点直接变为黑结点

  • 如果插入到红色结点的下面,此时破坏了性质4

  • case1:X的叔叔(即父亲的兄弟)是红色的

    image-20240306190448146

    这里所有的结点都带子树,至少会有NULL结点

    解决方法:由于父结点P和叔叔结点U都是红色的,寻求爷爷结点G的帮忙,最后将父结点P和叔叔结点U染黑,爷爷结点染红

    本质在于将问题向上推,最差的情况也就是将问题一直推到根结点,此时只需要将根结点染黑即可

    但是所有结点仍然满足性质,从该结点出发到后代叶结点的包含相同数量的黑结点

  • case2:X的叔叔是黑结点,且X是P的右孩子

  • case3:X的叔叔是黑结点,且X是P的左孩子

    image-20240306191302864

    image-20240306193628555

    对于case2到case3的转变,X绕P点进行右旋(RR rotation)

    对于case3情况,G点绕X点进行左旋(LL rotation),成功解决问题

    最后进行染色,根结点为黑色,第二层为红色

    对于case2,可以是直接使用LR旋转,只需要保证最终根结点为黑色,第二层为红色

    image-20240306191947022

    img

即如果插入后直接落入情况三,只需要一次旋转染色即可解决。直接落入情况二,一次旋转进入情况三,再一次旋转染色即可解决。但如果落入情况一,一次调整后可能还在情况一,可能直到最后都是通过情况一加上染黑根结点解决,也可能几次调整后进入情况二或三后解决。

定理: 一棵有n个内部结点的红黑树,插入一个结点的时间复杂度为\(O(logN)\)

根据上述流程,红黑树插入最多的旋转次数为2次(因为只有情况2 和3 会要旋转进入情况2 后1 次旋转必定进入情况3,进入情况3 后1 次旋转必定解决),改变颜色最多是\(O(logN)\)次,在情况2、3只需要染一次色,情况1最差也是每两层染一次色,因为我们已知红黑树的最大高度为\(O(logN)\)

因此插入操作,除了\(O(logN)\)的搜索时间外,仅剩常数次的旋转和\(O(logN)\)次的染色,最后仍为\(O(logN)\)的时间复杂度

image-20240306193014658

一定会有红色。数学归纳法进行证明

  1. \(n=2\)时,在黑色根结点的下方插入一个红结点,不产生任何负面影响,红色结点数+1
  2. 如果以后插入红结点后不需要调整,则红色结点数量增加
  3. 如果需要调整,也就是对应\(case1-3\)

    • 对于case1,\(X,G\)一定为红色结点,即使 最后\(G\)不断向上推移到根结点后被染黑,\(X\)依然保持红色
    • 对于case2,经过旋转,叔叔结点的近侄子变为远侄子,红色结点数不变
    • 对于case3,再次旋转后,可以保证根结点为黑色,第二层均为红结点

2.1.2 Delete

不考虑红黑树,BST结点删除的回顾

  • Delete a leaf node: reset its parent link to NULL 如果X没有孩子,直接删除就好

  • Delete a degree 1 node: replace the node by its single child 如果X只有一个孩子,那让孩子接替X的位置

  • Delete a degree 2 node:

    • Replace the node by the largest one in its left subtree or the smallest one in its right subtree.

    • Delete the replacing node from the subtree.

    如果X有两个孩子,那就让X与左子树的最大结点交换,然后删除X

    此时X所在位置最多只有一个孩子结点,因为左子树的最大值没有右孩子,右子树的最小值没有左孩子

红黑树的删除在此基础上展开,对于第三种情况,X与左子树的最大值进行交换,交换的仅仅是键值,颜色并没有交换,否则红结点可能换到根结点(虽然可以染黑),黑结点换到下方,极有可能造成性质5被破坏。

并且第三种情况经过交换后,会直接转化为第一、第二种情况。

对于情况一,接替被删除结点的是NULL

对于情况二,被删除结点的子结点接替位置。如果被删除结点为红结点(似乎不太可能,性质5,红结点的两个子结点都为黑色,要么都为叶子,要么都不为)。如果删除的是黑色,接替上来是红色,染黑即可,性质5仍然满足。 接替上来是黑色,这该如何处理,显然沿着子树的路径,黑色结点数少了一

为了解决删除黑色,接替结点为黑色,沿子树路径黑色结点数减一 Must add 1 black to the path of the replacing node.直接给替换上来的黑色再加一重黑色,变成双黑

以下X 在此处则表示双黑结点,图中用黑色圆圈和圆旁边的加号表示双黑,蓝色表示颜色无所谓,可红可黑。

  • case1:X的兄弟是红色的

image-20240304113340387

image-20240306204643985 $$ 父兄换色 + 右旋 $$ 由于原先的树满足红黑树定义第四条,因此此时父结点一定是黑色。我们的想法很简单,兄弟是红色,那就希望兄弟能两肋插刀,把兄弟转上去,为了保持红黑树性质,很可惜只能把父亲染红,,自己还承受双黑debuff。但是好处在于,这个问题转化为了接下来的情况二三四中的一种,我们来看如何解决。

  • case2:X的兄弟是黑色的,且兄弟的两个孩子(根据远近划分为远侄子和近侄子)都是黑色的

image-20240304113650455

image-20240306205303056

现在这个情况从P视角出发,满足性质5。所以为了缓减双黑这个debuff,我将双黑推给父亲,这时性质5被打破了,兄弟被迫变为红色

  • 如果父结点本身为红色,在双黑加持下,变为黑色,恰好弥补黑色少一的情况,问题解决
  • 如果父结点本身为黑色,那就变为双黑,问题向根结点靠近
  • 假如从case1变为case2,父结点一定为红色,那么问题直接解决

  • case3 : X的兄弟结点是黑色,但是近侄子是红色的,远侄子是黑色的

image-20240304113921535

image-20240306210046641

此时的红色在父亲\(P\)\(RL\)位置上,因此实现double rotation中的一个single rotation(左旋)就能得到\(RR\)情况

并且此处存在颜色变换

  • case4 : X 的兄弟是黑色的,且远侄子是红色的,近侄子颜色任意

image-20240304114030783

image-20240306212705846

此时对应AVL树的RR,于是再一次single rotation即可把双黑的一重黑丢给红色远侄子(即\(X,N_2\)都变成黑色),但为了保证红黑树性质的颜色变换,P和S还要交换颜色

image-20240306213132965

定理: 一棵有n个内部结点的红黑树删除一个结点的时间复杂度为\(O(logN)\)

  1. 我们最多使用\(O(logN)\)的时间找到删除结点,进行一次交换,后续进行删除操作
  2. 最坏进入删除黑色,接替点为黑色的情况,对于case1,如果进入case3,case4能快速解决,因为4 可以直接解决,3 直接进入4 然后解决。如果进入case2,也因为父结点为红色直接解决。
  3. 对于case2,可以因为父结点为红色直接解决,也能多次多次进入case2,每次向上推一格,由于树高为\(O(logN)\),所以最多进行\(O(logN)\)
  4. 对于case3,case4,均能快速解决。

image-20240306214350394

2.2 B+ Tree

B+ 树是一种用树状形式维护有序数列比较信息的数据结构,其增改操作拥相对于二叉树结构更加稳定的对数时间复杂度,通常用于数据库和操作系统的文件系统中。

image-20240306215547576

A B+ tree of order M is a tree with the following structural properties:

  1. The root is either a leaf or has between 2 and \(M\) children. 根要么是叶子,要么具有 2 和\(M\) 子级。
  2. All nonleaf nodes (except the root) have between$ ⌈M/2⌉$and M children. 所有非叶结点(根除外)都有 ⌈M/2⌉ 和 M 个子结点。
  3. All leaves are at the same depth. 所有叶子都处于相同的深度。

所有真实的数据都被存储在叶子结点中,形成一个有序的数列。而非叶子结点(不包含root)中第$i$个值等于第$i+1$颗子树的最小值。如上图所示表现为颜色相同的一对上下结点。因此非叶结点最多存 $M-1$个值。

抽象地来说就是,我们把一个数列相对均匀的分为 \(m\)块,然后把分界的数拿出来。当我们去查找或插入时,只需要和这些边界数进行比较,就知道它应该放在哪一块里。再不断细化粒度,用类似于\(m\)分”的思想来找到目标位置。

由于它在空间最浪费的情况下是一棵 \(\lceil M/2 \rceil\) 叉树,所以 B+ 树的深度是 \(O(\lceil \log_{\lceil M/2 \rceil} N \rceil)\)

2.2.1 查找

例如,我们在上面这棵树中找 43 这个值,橙色部分表示我们的焦点。

image-20240306220851541

我们发现有 21≤43<48,所以顺着标识的橙色指针向下。

image-20240306220945467

我们发现有 41≤43,所以顺着标识的橙色指针向下

image-20240306220957773

已经走到叶子结点,最后发现我们要找的 43

2.2.2 插入

Insert(46), no split

image-20240306221056756

image-20240306221106486

image-20240306221113847

找到要塞的位置了,发现要塞的地方是 45 的后面,插入以后发现一共 4 个数,而 �=4M=4,不需要分裂。

Insert(44), split

image-20240306221128282

image-20240306221137239

image-20240306221148709

找到要塞的位置了,发现要塞的地方是 45 的前面,插入以后发现一共 5 个数,而 �=4M=4,需要分裂!

image-20240306221158241

向上递归,我们悲痛地发现,这个节点在分裂后有了 5 个子节点,不得不再次分裂。

image-20240306221207458

向上递归,我的老天爷呀,怎么还没到头!这下我们要分裂根部了!

image-20240306221218515

由于根部被裂开了,所以我们需要添加一个新的根,这也意味着树的层数增高了。

现在,我们终于完成了插入。