跳转至

红黑树 , B+

红黑树

红黑树的定义满足以下性质:

  1. 每个结点要么是红的,要么是黑的。
  2. 根结点是黑的。
  3. 每个叶结点(NIL 结点,空结点)是黑的。
  4. 如果一个结点是红的,那么它的两个儿子都是黑的。
  5. 对每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点。

复杂度证明

Definition

  1. NIL 称为外部结点,其余有键值的结点称为内部结点。
  2. 从某个结点 \(X\) 出发到达一个叶子结点(NIL)的任意一条简单路径上的黑色结点个数(不含 \(X\) 本身)称为 \(X\) 的黑高,记为 \(\operatorname{bh}(X)\)。整棵红黑树的黑高是其根结点的黑高。

Theorem

一棵有 \(n\) 个内部结点的红黑树的高度至多为 \(2 \ln (n + 1)\)

Proof

对于任意结点 \(x\)\(\operatorname{sizeof}(x) \geqslant 2^{\operatorname{bh}(x)} - 1\). \(\operatorname{sizeof}(x)\) 表示以 \(x\) 为根的子树的内部结点个数。

\(\operatorname{bh}(T) \geqslant h(T) / 2\),其中 \(h(T)\) 表示树 \(T\) 的高度。

对于红黑树,黑高是绝对严格的平衡要求,而红色结点则是少量不平衡的因素,并且定义控制了红色结点的个数,也就控制了不平衡因素的影响,因此红黑树还是可以保持一定程度的平衡的。其插入,删除操作的时间复杂度都是 \(O(\log n)\)

插入

将情况递归到父亲的父亲。

删除

BST 的删除类似,寻找前驱或后继结点替换。

  • 递归到最后就是删除“叶子结点”。
  • 需要维持红黑树的性质。
    • 红色:直接删除。
    • 黑色:需要在到达此结点的简单路径上添加一个黑色结点,并且不改变其余结点的 \(\operatorname{bh}\)。依据兄弟与叔侄关系判断

兄弟为红色,只需要花费一次旋转操作,便可以归结为其他情况。

兄弟为黑色,且两个侄子也都是黑色,“将自己和兄弟的黑色传给父亲”。如果父亲是红色,则将父亲变为黑色,兄弟变为红色;如果父亲是黑色,则兄弟变为红色,并将情况递归到父亲。无需旋转。

兄弟为黑色,近侄子为红色,远侄子为黑色,“这样没法传黑色”。需要先将近侄子变为黑色,兄弟变为红色,然后进行一次右旋转,转变为 Case 4。(近侄子和兄弟的颜色变化不会影响内部的 \(\operatorname{bh}\)

兄弟为黑色,远侄子为红色,“最复杂的一种情况”。因为常规思路是哪里不平衡需要减轻其不平衡程度,但是这里的反而先加重了,因为最后会将“叶子结点”删去,需要有结点去平衡其删除后的影响。首先将父亲和兄弟的颜色互换,并且将远侄子变为黑色。然后对父亲进行一次左旋。注意到 \(\operatorname{bh}(c) = \operatorname{bh}(d) = 0\),且 \(\operatorname{bh}(b) = 1\),所以左旋后的树各结点的 \(\operatorname{bh}\) 不变。

B+

B+ 树是一种多路搜索树,其定义满足以下性质:

  • 根结点要么是叶子结点,要么有 \(2\) \(M\) 个子结点,其中 \(M\) 是树的阶数;
  • 除根结点外的所有非叶子结点有 \(\lceil M/2 \rceil\) \(M\) 个子结点;
  • 所有叶子结点拥有相同的深度(假设所有的非根的叶子结点也有 \(\lceil M/2 \rceil\) \(M\) 个子结点,子结点指键值)

插入

#ifndef _BP_TREE_H_
#define _BP_TREE_H_
#define MAX 3
#define MAXN 10005
typedef struct node *Tree;
typedef struct node
{
    int size;
    int isLeaf;
    int key[MAX + 1];
    Tree parent;
    Tree child[MAX + 1];
} Node;
int InsertValue(Tree p, int key);
Tree InsertLeaf(Tree root, int key);
Tree SplitLeaf(Tree p, Tree parent, Tree root, int key);
Tree InsertInternal(Tree p, Tree Lefttree, Tree Righttree, Tree root, int key);
void PrintTree(Tree root);
#endif
int InsertValue(Tree p, int key) {
    int i = 0;
    while (i < p->size && key > p->key[i]) {
        i++;
    }
    if (key == p->key[i]) {
        printf("Key %d is duplicated\n", key);
        return -1;
    };
    for (int j = p->size; j > i; j--) {
        p->key[j] = p->key[j - 1];
    }
    p->key[i] = key;
    p->size++;
    return i;
}
Tree InsertLeaf(Tree root, int key) {
    if (!root) {
        root = (Tree)malloc(sizeof(Node));
        root->size = 1;
        root->isLeaf = 1;
        root->key[0] = key;
        root->parent = NULL;
        for (int i = 0; i < MAX; i++) {
            root->child[i] = NULL;
        }
    } else {
        Tree p = root;
        Tree parent = NULL;
        while (!p->isLeaf) {
            parent = p;
            for (int i = 0; i < p->size; i++) {
                if (key < p->key[i]) {
                    p = p->child[i];
                    break;
                }
                if (i == p->size - 1) {
                    p = p->child[i + 1];
                    break;
                }
            }
        }
        if (p->size < MAX) {
            if (InsertValue(p, key) == -1) {
                return root;
            }
            p->parent = parent;
        } else {
            root = SplitLeaf(p, parent, root, key);
        }
    }
    return root;
}
Tree SplitLeaf(Tree p, Tree parent, Tree root, int key) {
    Tree LeftTree = (Tree)malloc(sizeof(Node));
    Tree RightTree = (Tree)malloc(sizeof(Node));
    if (InsertValue(p, key) == -1) {
        return root;
    }
    LeftTree->size = (MAX + 1) / 2;
    RightTree->size = MAX + 1 - (MAX + 1) / 2;
    LeftTree->isLeaf = RightTree->isLeaf = 1;
    for (int i = 0; i < LeftTree->size; i++) {
        LeftTree->key[i] = p->key[i];
    }
    for (int i = 0; i < RightTree->size; i++) {
        RightTree->key[i] = p->key[i + LeftTree->size];
    }
    if (p == root) {
        Tree newRoot = (Tree)malloc(sizeof(Node));
        newRoot->size = 1;
        newRoot->key[0] = RightTree->key[0];
        newRoot->isLeaf = 0;
        newRoot->child[0] = LeftTree;
        newRoot->child[1] = RightTree;
        root = newRoot;
        LeftTree->parent = RightTree->parent = newRoot;
    } else {
        root = InsertInternal(parent, LeftTree, RightTree, root, RightTree->key[0]);
    }
    return root;
}
Tree InsertInternal(Tree p, Tree LeftTree, Tree RightTree, Tree root, int key) {
    if (p->size < MAX - 1) { // insert directly
        int i = InsertValue(p, key);
        if (i == -1) {
            return root;
        }
        for (int j = p->size; j > i + 1; j--) {
            p->child[j] = p->child[j - 1];
        }
        p->child[i] = LeftTree;
        p->child[i + 1] = RightTree;
        LeftTree->parent = RightTree->parent = p;
    } else { // split internal
        Tree LeftChild = (Tree)malloc(sizeof(Node));
        Tree RightChild = (Tree)malloc(sizeof(Node));
        Tree ptrarray[MAX + 1];
        for(int i = 0; i < MAX + 1; i++) {
            ptrarray[i] = NULL;
        }
        for(int i = 0; i < MAX + 1; i++) {
            ptrarray[i] = p->child[i];
        }
        int i = InsertValue(p, key);
        if (i == -1) {
            return root;
        }
        for (int j = MAX + 1; j > i + 1; j--) {
            ptrarray[j] = ptrarray[j - 1];
        }
        ptrarray[i] = LeftTree;
        ptrarray[i + 1] = RightTree;
        LeftChild->isLeaf = RightChild->isLeaf = 0;
        // the size is number of children - 1
        LeftChild->size = (MAX + 1) / 2 - 1;
        RightChild->size = (MAX + 1) - (MAX + 1) / 2 - 1;
        // note that the most left branch of right child dose not need a key
        for (int i = 0; i < LeftChild->size; i++) {
            LeftChild->key[i] = p->key[i];
        }
        for (int i = 0; i < RightChild->size; i++) {
            RightChild->key[i] = p->key[i + LeftChild->size + 1];
        }
        for (int i = 0; i < LeftChild->size + 1; i++) {
            LeftChild->child[i] = ptrarray[i];
            LeftChild->child[i]->parent = LeftChild;
        }
        for (int i = 0; i < RightChild->size + 1; i++) {
            RightChild->child[i] = ptrarray[i + LeftChild->size + 1];
            RightChild->child[i]->parent = RightChild;
        }
        if (p == root) {
            Tree newRoot = (Tree)malloc(sizeof(Node));
            newRoot->size = 1;
            newRoot->key[0] = p->key[LeftChild->size];
            newRoot->isLeaf = 0;
            newRoot->child[0] = LeftChild;
            newRoot->child[1] = RightChild;
            root = newRoot;
            LeftChild->parent = RightChild->parent = newRoot;
        } else {
            root = InsertInternal(p->parent, LeftChild, RightChild, root, p->key[LeftChild->size]);
        }
    }
    return root;
}