红黑树
补充知识:平衡树
- 平衡树
- AVL 自平衡二叉查找树 或 平衡树
- 平衡因子
- 左旋(RR)、右旋(LL)、双向旋转(LR、RL)
- 插入
- 删除
- 查找
- B 树
- …
- AVL 自平衡二叉查找树 或 平衡树
- 红黑树
平衡树(Balance Tree,BT)
指的是,任意节点的子树的高度差都小于等于1。常见的符合平衡树的有,B树(多路平衡搜索树)、AVL树(二叉平衡搜索树)等。
AVL树
是最先发明的自平衡二叉查找树。在AVL树中任何节点的两个子树的高度最大差别为1,所以它也被称为高度平衡树。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。
AVL树可视化网站,方便学习理解:点击此处 。
我的 AVL 树代码参考了这里的部分实现:点此查看出处 。
为什么要平衡? 一个不具备平衡性的查找树可能退化成单链表,时间复杂度会到 O ( N ) O(N) O(N)
平衡因子
某结点的左子树与右子树的高度差即为该结点的平衡因子(BF,Balance Factor)。平衡二叉树上所有结点的平衡因子只可能是 -1,0 或 1。
旋转
找到由于在二叉树上插入节点而失去平衡的最小子树,进行旋转操作。
关于修改该子树的父节点指向新的根节点:
- 我们可以传入该子树的父节点指针 p ,然后让 p 的原来指向该子树的指针 q 指向新子树的根节点
- 也可以在子树旋转完成后,返回该子树的根节点,然后 p->q 指向新的根节点。(方便起见,使用这种方法)
右旋(LL):因为向左子树(L)的左孩子(L)添加节点,导致不平衡,进行右旋操作。(此时根节点旋转为左子树的右儿子,右旋)
- 找到最小子树 A 的左儿子 B
- A 的左儿子更新为 B 的右儿子
- B 的右儿子更新为 A
- 返回新的根节点 B
Node* rightRotate(Node* r) {
Node* left = r->left;
r->left = left->right;
left->right = r;
/*重新计算高度并更新
...
*/
return left;
}
左旋(RR):因为向右子树(R)的右孩子(R)添加节点,导致不平衡,进行左旋操作。(此时根节点旋转为右子树的左儿子,右旋)
- 找到最小子树 A 的右儿子 C
- A 的右儿子更新为 C 的左儿子
- C 的左儿子更新为 A
- 返回新的根节点 C
双向旋转(先左后右)平衡处理LR:由于在 A 的左子树(L)的右孩子(R)插入了新结点,则需进行两次旋转(先左旋后右旋)操作。
双向旋转(先右后左)平衡处理RL:原理同上 LR 。
插入
每次 AVL 旋转都耗费恒定的时间,插入处理在整体上耗费 O(log n) 时间。 向AVL树插入时像二叉查找树一样进行插入操作,接着自底向上把不平衡的所有节点进行旋转调整。
描述如下:
- 若当前树为空树,则插入一个数据元素为 value 的新结点作为根结点,树的深度增1;
- 若 value 等于当前根结点的关键字,则不进行;
- 若 value 小于当前根结点的关键字,而且其左子树不存在和 value 相同关键字的结点,则将 value 插入其左子树。当插入后其左子树深度增加时 ,讨论以下情况(当前根节点的平衡因子,指的是子树修改完了高度和平衡因子相关信息,现在回溯到当前根节点,正要修改当前根节点的高度和平衡因子信息 ):
- 当前根结点的平衡因子为 -1(右子树的深度大于左子树的深度),则将根结点的平衡因子更改为0,当前根结点的深度不变;
- 当前根结点的平衡因子为 0(左、右子树的深度相等),则将当前根结点平衡因子更改为1,当前根结点的深度增1;
- 当前根结点的平衡因子为 1(左子树的深度大于右子树的深度),则讨论以下情况:
- 插入左子树完成后,如果左子树根结点的平衡因子变为 1,则需进行单向右旋平衡处理,并且在右旋处理之后,更新高度和平衡因子
- 插入左子树完成后,如果左子树根结点的平衡因子变为 - 1,则需进行两次旋转 LR 平衡处理,第一次旋转(左旋)单独操作这棵左子树,第二次旋转(右旋)以当前根结点为根节点的子树,然后更新高度和平衡因子
- 若 value 大于当前根结点的关键字,而且在当前根结点的右子树中不存在和 value 有相同关键字的结点,则将 value 插入在当前根节点的右子树上,当插入后其右子树深度增加时 ,讨论以下情况:
- 当前根结点的平衡因子为 - 1(右子树的深度大于左子树的深度),则讨论以下情况:
- 插入右子树完成后,如果右子树根结点的平衡因子变为 - 1,则需进行单向左旋平衡处理,并且在左旋处理之后,更新高度和平衡因子
- 插入右子树完成后,如果右子树根结点的平衡因子变为 1,则需进行两次旋转 RL 平衡处理,第一次旋转(右旋)单独操作这棵右子树,第二次旋转(左旋)以当前根结点为根节点的子树,然后更新高度和平衡因子
- 当前根结点的平衡因子为 0(左、右子树的深度相等),则将当前根结点平衡因子更改为 - 1,当前根结点的深度增1;
- 当前根结点的平衡因子为 1(右子树的深度大于左子树的深度),则将根结点的平衡因子更改为0,当前根结点的深度不变;
- 当前根结点的平衡因子为 - 1(右子树的深度大于左子树的深度),则讨论以下情况:
删除
此处给出两种方法(我采用的第一种):
- 找到要删除的节点,找到后开始删除,从这个结点开始把当前根节点不断向下旋转,当旋转为叶子节点时(左右子树都为 NULL)直接删除,回溯的时候再调整树。旋转是常数操作,最多旋转 $log; n $ 次,回溯调整最多也是 l o g n log n logn 级别的,因此整个删除是 O ( l o g n ) O(log\;n) O(logn) 的。(这里只给出大致思路,有一个猜想,旋转左右旋转都可以,如果我们规定删除时左旋向下,那么调整是不是只要每个根节点都右旋回去即可,感觉应该是没问题的……但是有待验证)
- 找到要删除的节点
- 如果该节点有右子树,则在右子树中找到值最小的节点
node
和当前节点交换,然后删除node
即可。 - 如果没有,直接左子树代替当前节点,然后删除当前节点即可。
- 回溯每个节点都要检查平衡因子是否满足条件,然后进行调整。最多调整 l o g n log\;n logn 次。
- 如果该节点有右子树,则在右子树中找到值最小的节点
查找
和二叉查找(二叉搜索树)完全相同。
一些结论
代码中左旋右旋之后直接更新了一些节点的平衡因子,我们不难可以推导出。在单向旋转的情况下,有些节点总是变为固定的值,有些则是在变化中恒定不变的,可以自己画图推导一下:
- 单项右旋:新的根节点和其新的右子树的根节点平衡因子一定为 0,新的根节点的左子树和新树剩余其他节点平衡因子及高度不变。
- 单项左旋:新的根节点和其新的左子树的根节点平衡因子一定为 0,新的根节点的右子树和新树剩余其他节点平衡因子及高度不变。
但是这个结论在双向旋转操作中并不适用,但也是有规律的:
- 旋转之前,根节点左儿子的右儿子的平衡因子(或者根节点右儿子的左儿子的平衡因子)为 0 的时候,通过旋转操作得到的最后各个节点的平衡因子是正确的,这种情况不需要额外处理。
- LR:
- 旋转之前,根节点左儿子的右儿子的平衡因子为 1,则旋转后新根节点为 0,其左子树根节点为 0,其右子树根节点为 -1。(可以画图推导,下面的类似)
- 旋转之前,根节点左儿子的右儿子的平衡因子为 -1,则旋转后新根节点为 0,其左子树根节点为 1,其右子树根节点为 0。
- RL:
- 旋转之前,根节点右儿子的左儿子的平衡因子为 1,则旋转后新根节点为 0,其左子树根节点为 0,其右子树根节点为 -1。
- 旋转之前,根节点右儿子的左儿子的平衡因子为 -1,则旋转后新根节点为 0,其左子树根节点为 1,其右子树根节点为 0。
代码
#include <iostream>
#include <cstdio>
#include <stdlib.h>
#include <assert.h>
#include <queue>
using namespace std;
struct node {
int data;
int height;
int factor;
struct node* left;
struct node* right;
};
typedef struct node node_t;
typedef struct node* nodeptr_t;
// 获取高度
int treeHeight(nodeptr_t root) {
if (root) return root->height;
return 0;
}
void updateHeight(nodeptr_t root) {
if (!root) return;
// 重置高度,重新计算
root->height = 1;
if (root->left) root->height = root->left->height + 1;
if (root->right) root->height = max(root->height, root->right->height + 1);
}
// 获取平衡因子
int treeGetBalanceFactor(nodeptr_t root) {
if(root == nullptr) {
return 0;
}
else {
return root->factor;
}
}
// 重新计算平衡因子
int calBalanceFactor(nodeptr_t root) {
if(root == nullptr) {
return 0;
}
else {
return root->factor = (treeHeight(root->left) - treeHeight(root->right));
}
}
// 右旋操作:根节点旋转为左儿子的右儿子,返回新的根节点
nodeptr_t treeRightRotate(nodeptr_t root) {
nodeptr_t left = root->left;
root->left = left->right;
left->right = root;
// 更新平衡因子
left->factor = 0;
left->right->factor = 0;
return left;
}
// 左旋操作:根节点旋转为右儿子的左儿子,返回新的根节点
nodeptr_t treeLeftRotate(nodeptr_t root) {
nodeptr_t right = root->right;
root->right = right->left;
right->left = root;
// 更新平衡因子
right->factor = 0;
right->left->factor = 0;
return right;
}
// 对当前根节点检查平衡因子,并根据情况进行调整,返回新的根节点
nodeptr_t treeRebalance(nodeptr_t root) {
int factor = treeGetBalanceFactor(root);
if(factor > 1 && treeGetBalanceFactor(root->left) > 0) {
// LL
root = treeRightRotate(root);
}
else if(factor > 1 && treeGetBalanceFactor(root->left) < 0) {
// LR
// 需要讨论 根节点左儿子的右儿子的平衡因子(或者高度)
int flag = root->left->right->factor;
root->left = treeLeftRotate(root->left);
root = treeRightRotate(root);
// 如果 flag 不是 0,则他每次旋转修改的平衡因子都和单向旋转不同
if (flag == 1) {
// 新插入的节点在原来的 根节点左儿子的右儿子的左边
root->left->factor = 0;
root->right->factor = -1;
} else if (flag == -1){
root->left->factor = 1;
root->right->factor = 0;
}
} else if(factor < -1 && treeGetBalanceFactor(root->right) < 0) {
// RR
root = treeLeftRotate(root);
}
else if(factor < -1 && treeGetBalanceFactor(root->right) > 0) {
// RL
// 需要讨论 根节点右儿子的左儿子的平衡因子(或者高度)
int flag =root->right->left->factor;
root->right = treeRightRotate(root->right);
root = treeLeftRotate(root);
// 如果 flag 不是 0,则他每次旋转修改的平衡因子都和单向旋转不同
if (flag == 1) {
// 新插入的节点在原来的 根节点右儿子的左儿子的左边
root->left->factor = 0;
root->right->factor = -1;
} else if (flag == -1) {
root->left->factor = 1;
root->right->factor = 0;
}
} else {
// Nothing happened.
}
if (!root) return nullptr;
// 可以发现任何旋转操作最多只改变三个节点的高度
// 重新计算这三个点高度即可
updateHeight(root->left);
updateHeight(root->right);
updateHeight(root);
return root;
}
void treeInsert(nodeptr_t* rootptr, int value)
{
nodeptr_t newNode;
if((*rootptr) == nullptr) {
newNode = new node_t();
assert(newNode);
newNode->data = value;
newNode->left = newNode->right = NULL;
newNode->height = 1;
newNode->factor = 0;
*rootptr = newNode;
} else if((*rootptr)->data == value) {
return;
} else {
if((*rootptr)->data < value) {
treeInsert(&((*rootptr)->right),value);
if ((*rootptr)->right->height + 1 > (*rootptr)->height) {
(*rootptr)->height = (*rootptr)->right->height + 1;
}
}
else {
treeInsert(&((*rootptr)->left),value);
if ((*rootptr)->left->height + 1 > (*rootptr)->height) {
(*rootptr)->height = (*rootptr)->left->height + 1;
}
}
calBalanceFactor(*rootptr);
*rootptr = treeRebalance(*rootptr);
}
}
// 删除节点
void treeDelete(nodeptr_t* rootptr, int value)
{
if(*rootptr) {
if((*rootptr)->data == value) {
if ((*rootptr)->left && (*rootptr)->right) {
*rootptr = treeLeftRotate(*rootptr);
treeDelete((&(*rootptr)->left), value);
*rootptr = treeRightRotate(*rootptr);
updateHeight((*rootptr)->left);
updateHeight((*rootptr)->right);
updateHeight(*rootptr);
calBalanceFactor((*rootptr)->left);
calBalanceFactor((*rootptr)->right);
calBalanceFactor(*rootptr);
} else if ((*rootptr)->left && !(*rootptr)->right) {
auto temp = (*rootptr);
(*rootptr) = (*rootptr)->left;
delete temp;
} else if (!(*rootptr)->left && (*rootptr)->right) {
auto temp = (*rootptr);
(*rootptr) = (*rootptr)->right;
delete temp;
} else if (!(*rootptr)->left && !(*rootptr)->right) {
delete (*rootptr);
(*rootptr) = nullptr;
}
} else {
if((*rootptr)->data < value && (*rootptr)->right) {
treeDelete(&((*rootptr)->right),value);
// cout << ((*rootptr)->right) << endl;
updateHeight((*rootptr)->right);
calBalanceFactor((*rootptr)->right);
} else if ((*rootptr)->data > value && (*rootptr)->left) {
treeDelete(&((*rootptr)->left),value);
updateHeight((*rootptr)->left);
calBalanceFactor((*rootptr)->left);
} else {
// 查无此数
}
}
updateHeight(*rootptr);
treeRebalance(*rootptr);
} else {
// 查无此数
}
}
// 遍历就是二叉树的层序遍历
void printAVL(nodeptr_t* root) {
queue<nodeptr_t*> q;
q.push(root);
q.push(nullptr);
nodeptr_t space = new node_t({-1, -1, -1, 0, 0});
cout << "printing avl..." << endl;
while (q.size()) {
auto temp = q.front();
q.pop();
if (temp == nullptr) {
cout << endl;
if (q.size()) q.push(nullptr);
} else {
if ((*temp)->data != -1) {
printf("%02d ", (*temp)->data);
if ((*temp)->left) q.push(&(*temp)->left);
else q.push(&space);
if ((*temp)->right) q.push(&(*temp)->right);
else q.push(&space);
} else {
cout << " ";
}
}
}
}
void printAVLH(nodeptr_t* root) {
queue<nodeptr_t*> q;
q.push(root);
q.push(nullptr);
nodeptr_t space = new node_t({-1, -1, -1, 0, 0});
cout << "printing avlh..." << endl;
while (q.size()) {
auto temp = q.front();
q.pop();
if (temp == nullptr) {
cout << endl;
if (q.size()) q.push(nullptr);
} else {
if ((*temp)->data != -1) {
printf("%02d ", (*temp)->height);
if ((*temp)->left) q.push(&(*temp)->left);
else q.push(&space);
if ((*temp)->right) q.push(&(*temp)->right);
else q.push(&space);
} else {
cout << " ";
}
}
}
}
void printAVLF(nodeptr_t* root) {
queue<nodeptr_t*> q;
q.push(root);
q.push(nullptr);
nodeptr_t space = new node_t({-1, -1, -1, 0, 0});
cout << "printing avlf..." << endl;
while (q.size()) {
auto temp = q.front();
q.pop();
if (temp == nullptr) {
cout << endl;
if (q.size()) q.push(nullptr);
} else {
if ((*temp)->data != -1) {
printf("%02d ", (*temp)->factor);
if ((*temp)->left) q.push(&(*temp)->left);
else q.push(&space);
if ((*temp)->right) q.push(&(*temp)->right);
else q.push(&space);
} else {
cout << " ";
}
}
}
}
int main() {
int n;
nodeptr_t root = nullptr;
cin >> n;
for (int i = 0; i < n; ++ i ) {
int x;
cin >> x;
if (root == nullptr) {
root = new node_t({x, 1, 0, nullptr, nullptr});
} else {
treeInsert(&root, x);
}
}
printAVL(&root);
printAVLH(&root);
printAVLF(&root);
// treeDelete(&root, 6);
// printAVL(&root);
// printAVLH(&root);
return 0;
}
自己测了一些,没啥把握,如果有错误数据,可以评论发出来,研究一下代码 BUG。
test1:9 50 40 60 55 70 30 58 53 57
test2:6 50 40 60 55 70 58
四种基本临界结构的测试:
3 50 40 10
3 50 20 30
3 50 60 70
3 50 60 55
优化
结构很乱,优化余地很多,感觉有不少冗余操作,欢迎讨论和给出建议。
其他实现
AVL 树的平衡因子可以考虑更换为随机权重,我们每次插入时按照二叉搜索树的操作进行插入,插入完成后,我们根据权重按最小堆(或者最大堆)的方式调整堆(上浮或者下浮更改为左旋或者右旋)。
以下两点引自博文,对这种实现有详细介绍:点击此处 。
- 这种方法是二叉搜索树和堆合并构成的新数据结构,取了Tree和Heap各一半,称为 Treap。
- 对于一般的二叉搜索树,在某些特殊情况下根据输入数据来建树有可能退化为一条链,比如一个依次增大的数列。而如果一棵二叉排序树的节点是按照随机顺序插入,得到的二叉排序树大多数情况下是平衡的,其期望高度是 O ( l o g n ) O(log\;n) O(logn)。因此 Treap 利用 weight 值作为随机因子来调整二叉树的形状,使得在大部分情况下比直接通过数据建立的二叉树要平衡。
红黑树
红黑树是一种特化的AVL树(平衡二叉树),都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。
它虽然是复杂的,但它的最坏情况运行时间也是非常良好的,并且在实践中是高效的: 它可以在 O ( l o g n ) O(log\; n) O(logn) 时间内做查找,插入和删除,这里的 n 是树中元素的数目。
左右子树高差有可能大于 1,所以红黑树不是严格意义上的平衡二叉树(AVL),但对之进行平衡的代价较低, 其平均统计性能要强于 AVL 。
由于每一棵红黑树都是一颗二叉排序树,因此,在对红黑树进行查找时,可以采用普通二叉排序树上的查找算法,在查找过程中不需要颜色信息。
此处可视化网站,可以参考学习,点击此处 。
应用
- Linux非实时任务调度中的应用
- Linux 的稳定内核版本在 2. 6. 24 之后。使用了新的调度程序 CFS,所有非实时可运行进程都以虚拟运行时间为 key 值挂在一棵红黑树上,以完成更公平高效地调度所有任务。根据所有任务占用 CPU 时间的状态来确定调度任务优先级。
- Linux虚拟内存中的应用
- 32 位 Linux 内核虚拟地址空间划分 0 - 3G 为用户空间,3 - 4G 为内核空间,因此每个进程可以使用 4GB的虚拟空间。同时,Linux 定义了虚拟存储区域( VMA) 以便于更好表示进程所使用的虚拟空间,每个 VMA是某个进程的一段连续虚拟空间,其中的单元具有相同的特征,所有的虚拟区域按照地址排序由指针链接为一个链表。当发生缺页中断时搜索 VMA 到指定区域时,则需要频繁操作,因此选用了红黑树以减少查找时间。
定义
- 每个节点是红色或黑色的
- 根节点是黑色的
- 每个叶子节点是黑色的
- 如果一个节点是红色的,则它儿子一定是黑色的
- 任一节结点其每个叶子节点的所有路径都包含相同数目的黑色结点。
前世今生
2-3-4 树、2-3 树和红黑树有着密不可分的关系,参考链接 。
转化
红黑树是对概念模型 2-3-4 树的一种实现。
2 节点直接转化为黑色节点;3 节点这里可以有两种表现形式,左倾红节点或者右倾红节点;4 节点被强制要求转化为一个黑父带着左右两个红色儿子。
左倾红黑树:这里只研究 2-3 树中较为特殊的一种转化 – 左倾红黑树。顾名思义,左倾红黑树限制了如果在树中出现了红色节点,那么这个节点必须是左儿子。
- 只要把左倾红黑树中的红色节点顺时针方向旋转45°使其与黑父平行,然后再将它们看作一个整体,就会发现,红黑树其实就是一颗 2-3树(只有 2 节点和 3 节点构成的树)。
- 左倾红黑树要求概念模型中的3节点在红黑树中必须用左倾的红色节点来表示。这种限定能够很大的减少红黑树调整过程中的复杂性。
插入
插入操作需要遵循一个原则:先将这个元素尝试性地放在已经存在的节点中,如果要存放的节点是 2 节点,那么插入后会变成 3 节点,如果要存放的节点是 3 节点,那么插入后会变成4节点(临时)。然后,我们对可能生成的临时 4 节点进行分裂处理(向上提取中间元素,形成一个 3 节点和一个 2 节点),使得临时4节点消失。
- 这正对应了红黑树在插入的时候一定会把待插入节点涂成红色,因为红色节点的意义是与父节点进行关联,形成概念模型2-3树中的3节点或者临时4节点。
- 而红黑树之所以需要在插入后进行调整,正是因为可能存在着概念模型中的临时4节点(反应在红黑树中是双红的情况)。
删除
- 3 节点,直接删除,变为 2 节点,不管是删除的红色还是黑色,最终都只留下黑色节点,不产生任何影响
- 删除 2 节点的时候,会使得树中某条路径的高度(黑高)发生变化,树变得不平衡。
- 先删除这个2节点,然后对树进行平衡调整。
- 想办法让这个被删除的元素不可能出现在2节点中。(这里选择这种)
在搜索到这个节点的路径中,不断地判断当前节点是否为 2 节点,如果是,就从它的兄弟节点或者它的父节点借一个元素,使得当前节点由 2 节点成为一个 3 节点(父节点借兄弟节点一个,当前节点借父节点一个)或者一个临时 4 节点(和兄弟 2 节点合并)。除非当前节点是根节点,否则当前节点的父节点一定是一个非2节点。
2-3 树看红黑树定义的意义
任意节点到叶子节点经过的黑色节点数目相同:
【红黑树中的红节点是和黑色父节点绑定的,在 2-3 树中本来就是同一层的,只有黑色节点才会在 2-3 树中真正贡献高度,由于 2-3 树的任一节点到空链接距离相同,因此反应在红黑树中就是黑色完美平衡】
不会有连续的红色节点:
【2-3 树中本来就规定没有 4 节点,2-3-4 树中虽然有 4 节点,但是要求在红黑树中体现为一黑色节点带两红色儿子,分布左右,所以也不会有连续红节点】
左倾红黑树的插入
- 待插入元素插在了黑父的右边,而黑父左边是红色儿子。这种情况会导致在红黑树中出现右倾红节点。这种情况对应着 2-3 树中出现了临时4节点,我们在 2-3 树中的处理是将这个临时 4 节点分裂,左右元素各自形成一个 2 节点,中间元素上升到上层跟父节点结合。所以,我们在红黑树中的动作是,将原本红色的左右儿子染黑(左右分裂为 2 节点),将黑父染红(等待上升结合)。
- 插入元素比红父小,且红父自身就是左倾。就是红父和待插入元素同时靠在了左边,形成了连续的红节点。需要用两步来调整:由于我们插入的是红色节点,其实不会破坏黑色平衡。
- 首先对红父的父亲进行一次右旋
- 然后将旋转为右儿子的原来的根节点和新根节点颜色交换。消除了连续红色,并且这个操作依旧维持了黑色平衡。现在我们已经得到了情况1的场景,直接按情况1处理即可。
- 待插入元素比红父大,且红父自身就是左倾。即插入的这个节点形成了一个右倾的红色节点。将红父进行一次左旋,就能使得右倾红节点变为左倾,现在出现了连续的左倾红节点,直接按情况2处理即可。
左倾红黑树的删除
当我们要删除某个节点的时候选择它的前驱节点或者后继节点元素来替代它,转而删除它的前驱/后继节点。(这里选用后继结点)
每次都保证当前的节点是 2-3 树中的非 2 节点,如果当前节点已经是非 2 节点,那么直接跳过;如果当前节点是 2 节点,那么根据兄弟节点的状况来进行调整:
- 如果兄弟是 2 节点,那么从父节点借一个元素给当前节点,然后与兄弟节点一起形成一个临时 4 节点。
- 如果兄弟是非 2 节点,那么兄弟上升一个元素到父节点,同时父节点下降一个元素到当前节点,使得当前节点成为一个 3 节点。
这样的策略能够保证最后走到待删除节点的时候,它一定是一个非 2 节点,我们可以直接将其元素删除。
修复工作:由于红黑树定义的限制,我们在调整的过程中出现了一些本不该存在的红色右倾节点(因为生成了概念模型中的临时4节点),于是我们顺着搜索的方向向上回溯,如果遇到当前节点具备右倾的红色儿子,那么对当前节点进行一次左旋,这时原本的右儿子会来到当前节点的位置,然后将右儿子与当前节点交换颜色,我们就将右倾红节点修复成了左倾红节点,同时我们并没有破坏黑色节点的平衡。
总结:
- 3 个一组,左插左旋,右插右旋,中间晋升;
红黑树的插入删除的调整
插入
分为两种情况,父节点是祖父节点的左儿子或右儿子,下面讨论左儿子,右儿子的情况左右取反即可。 插入按照正常二叉查找树查找插入即可。
- (Case 1)叔叔是红色 :叔和父节点都改为黑色,祖父改为红色, 当前插入节点的指针移向祖父,把祖父这棵子树看作新插入的节点,向上修复。(如果插入到根节点,直接结束,然后修改根节点颜色为黑色;如果父节点为根节点,直接退出,不影响平衡)
- (Case 2)叔叔是黑色,且当前节点是右孩子 :新插入的节点指向父亲,做一次左旋,转化为 Case 3。
- (Case 3)叔叔是黑色,且当前节点是左孩子 :父节点变为黑色,祖父变为红色,当前指针指向祖父,做一次右旋,即可实现平衡。
删除
删除按照正常的二叉查找树找到之后,如果双子非空用后继结点替代,否则用子树替代即可。调整的时候分为两种情况:
- 双子非空,从被删除的替代节点如果为黑色,则才有意义调整,就从其原来位置开始调整
- 双子不全空,原来被删除的节点为黑色才有意义调整,直接从该位置新的节点开始调整。
- 只有一个节点,即根节点,直接删除即可。
调整:
- 当前节点为 红 + 黑,设置当前节点为黑色,结束。
- 当前节点为 黑 + 黑,且是根节点,直接结束。
- 当前节点为 黑 + 黑,且不为根节点。(又分为两种情况,当前节点是父节点的左儿子或右儿子,下面讨论左儿子,右儿子的情况左右取反即可。)
- (Case 1)x是"黑+黑"节点,x的兄弟节点是红色
- step1. 将当前节点的兄弟节点设为黑色
- step2. 将当前节点的父节点设为红色
- step3. 对当前节点父节点左旋
- step4. 问题转化为了,当前节点位置不变,但改变了其兄弟节点
- (Case 2) x是"黑+黑"节点,x的兄弟节点是黑色,x的兄弟节点的两个孩子都是黑色
- step1. 将当前节点的兄弟节点设为红色
- step2. 将当前节点的父节点设为当前节点(兄弟节点为根的子树也缺一个黑节点,当前节点也缺一个黑节点,转化为了根节点缺一个黑节点)
- (Case 3)x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的左孩子是红色,右孩子是黑色的
- step1. 将当前节点的兄弟节点的左孩子设为黑色
- step2. 将当前节点的兄弟节点设为红色
- step3. 对当前节点的兄弟节点右旋
- step4. 问题转化为了,当前节点位置不变,但改变了其兄弟节点为黑色,其右孩子为红色(Case 4)
- (Case 4)x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的右孩子是红色的,x的兄弟节点的左孩子任意颜色
- step1. 将当前节点的兄弟节点设为其父节点的颜色
- step2. 将当前节点的兄弟节点的右孩子设为黑色(兄弟节点的颜色)
- step3. 将当前节点的父节点设为黑色(兄弟节点的颜色)
- step4. 对当前节点父节点左旋
- step5. 设当前节点指向根节点
- (Case 1)x是"黑+黑"节点,x的兄弟节点是红色
实现
参考自其他博客的实现
点击此处查看。
代码实现的很多内容参考自上述博客,写的非常好,强烈推荐!此处并没有完全照搬,只实现了部分函数(足以运行测试),其中删除没有参考这篇博客,而是参考的《算法导论》的伪码。
TIP:模板的定义最好放在头文件中,否则会出错。详情参考此处。
#include <functional>
#include <iostream>
#include <iomanip>
using namespace std;
enum RBTColor{RED, BLACK};
template<class T>
class RBTNode {
public:
RBTNode() = default;
RBTNode(T value, RBTColor c, RBTNode* p, RBTNode* l, RBTNode* r):
key(value), color(c), parent(p), left(l), right(r) {};
~RBTNode() =default;
public:
RBTColor color;
T key;
RBTNode* left;
RBTNode* right;
RBTNode* parent;
};
template<class T>
class RBTree {
public:
RBTree() = default;
RBTree(std::function<int(T a, T b)> cmp) : _cmp(cmp) {
if (_cmp == NULL) {
_cmp = [](T a, T b) -> int {
if (a == b) return 0;
else if (a < b) return 1;
else return -1;
};
}
};
~RBTree() = default;
void preOrder();
void inOrder();
void postOrder();
RBTNode<T>* search(T key);
// RBTNode<T>* iterativeSearch(T key);
T minimum();
T maximum();
void insert(T key);
void remove(T key);
// void destroy();
void print();
private:
void preOrder(RBTNode<T>* root) const;
void inOrder(RBTNode<T>* root) const;
void postOrder(RBTNode<T>* root) const;
RBTNode<T>* search(RBTNode<T>* root, T key) const;
// RBTNode<T>* iterativeSearch(RBTNode<T>* root, T key) const;
// RBTNode<T>* minimum(RBTNode<T>* root) const;
// RBTNode<T>* maximum(RBTNode<T>* root) const;
// RBTNode<T>* successor(RBTNode<T>* x);
// RBTNode<T>* predecessor(RBTNode<T>* x);
void leftRotate(RBTNode<T>*& root, RBTNode<T>* x);
void rightRotate(RBTNode<T>*& root, RBTNode<T>* x);
void insert(RBTNode<T>*& root, RBTNode<T>* node);
void insertFixUp(RBTNode<T>*& root, RBTNode<T>* node);
void transplant(RBTNode<T>*& root, RBTNode<T>* x, RBTNode<T>* y);
void remove(RBTNode<T>*& root, RBTNode<T>* node);
void removeFixUp(RBTNode<T>*& root, RBTNode<T>* node);
void destroy(RBTNode<T>* root);
void print(RBTNode<T>*& root, T key, int direction);
private:
RBTNode<T>* _root;
std::function<int(T a, T b)> _cmp;
};
#define rb_parent(r) ((r)->parent)
#define rb_color(r) ((r)->color)
#define rb_is_red(r) ((r)->color==RED)
#define rb_is_black(r) ((r)->color==BLACK)
#define rb_set_black(r) do { (r)->color = BLACK; } while (0)
#define rb_set_red(r) do { (r)->color = RED; } while (0)
#define rb_set_parent(r, p) do { (r)->parent = p; } while (0)
#define rb_set_color(r, c) do { (r)->color = c; } while (0)
// 左旋
template<class T>
void RBTree<T>::leftRotate(RBTNode<T>*& root, RBTNode<T>* x) {
RBTNode<T>* y = x->right;
// 把 y 的左儿子给旧根节点
x->right = y->left;
if (y->left != NULL) {
y->left->parent = x;
}
// 把 x 的父节点指向 y
y->parent = x->parent;
if (x->parent == NULL) {
root = y;
} else {
if (x->parent->left == x) {
x->parent->left = y;
} else {
x->parent->right = y;
}
}
// 修改 y 左儿子指向 x
y->left = x;
// 修改 x 的父节点为 y
x->parent = y;
}
// 右旋
template<class T>
void RBTree<T>::rightRotate(RBTNode<T>*& root, RBTNode<T>* x) {
RBTNode<T>* y = x->left;
x->left = y->right;
if (y->right != NULL) {
y->right->parent = x;
}
y->parent = x->parent;
if (x->parent == NULL) {
root = y;
} else {
if (x->parent->left == x) {
x->parent->left = y;
} else {
x->parent->right = y;
}
}
y->right = x;
x->parent = y;
}
// 插入的外部接口
template<class T>
void RBTree<T>::insert(T key) {
RBTNode<T>* node;
if ((node = new RBTNode<T>(key, RED, NULL, NULL, NULL)) == NULL) {
cout << "ERROR: creating node failed !" << endl;
return;
}
insert(_root, node);
}
// 插入内部实现
template<class T>
void RBTree<T>::insert(RBTNode<T>*& root, RBTNode<T>* node) {
RBTNode<T>* x = root;
RBTNode<T>* y = NULL;
while (x != NULL) {
y = x;
if (node->key < x->key) {
x = x->left;
} else {
x = x->right;
}
}
node->parent = y;
if (y != NULL) {
if (node->key < y->key) {
y->left = node;
} else {
y->right = node;
}
} else {
_root = node;
}
node->color = RED;
insertFixUp(root, node);
}
// 添加后的修正
template<class T>
void RBTree<T>::insertFixUp(RBTNode<T>*& root, RBTNode<T>* node) {
RBTNode<T>* parent, * gparent;
while ((parent = rb_parent(node)) && rb_is_red(parent)) {
gparent = rb_parent(parent);
if (gparent->left == parent) {
// case1 叔节点和父节点都是红色
{
RBTNode<T>* uncle = gparent->right;
if (uncle && rb_is_red(uncle)) {
rb_set_black(parent);
rb_set_black(uncle);
rb_set_red(gparent);
node = gparent;
continue;
}
}
// case2 叔节点黑色,当前节点为父节点右孩子,转化为 case3
if (parent->right == node) {
leftRotate(root, parent);
swap(node, parent);
// 把旋转下去的红色根节点看作新插入节点
}
// case3
rb_set_black(parent);
rb_set_red(gparent);
rightRotate(root, gparent);
} else {
// case1
{
RBTNode<T>* uncle = gparent->left;
if (uncle && rb_is_red(uncle)) {
rb_set_black(parent);
rb_set_black(uncle);
rb_set_red(gparent);
node = gparent;
continue;
}
}
// case2
if (parent->left == node) {
rightRotate(root, parent);
swap(parent, node);
}
// case3
rb_set_black(parent);
rb_set_red(gparent);
leftRotate(root, gparent);
}
}
rb_set_black(root);
}
// 替换, x 的父节点指向 y , y 指向 x 的父节点。
// 但不修改 x 的指向,即 x 仍指向其原来父节点。
template<class T>
void RBTree<T>::transplant(RBTNode<T>*& root, RBTNode<T>* x, RBTNode<T>* y) {
if (rb_parent(x) == NULL) {
_root = y;
} else if (rb_parent(x)->left == x) {
rb_parent(x)->left = y;
} else {
rb_parent(x)->right = y;
}
if (y != NULL) y->parent = x->parent;
}
// 删除元素的外部接口
template<class T>
void RBTree<T>::remove(T key) {
RBTNode<T>* node;
if ((node = search(_root, key)) == NULL) {
cout << "ERROR: trying to delete " << key << " failed because there's not that value" << endl;
return ;
}
remove(_root, node);
}
// 删除内部实现
template<class T>
void RBTree<T>::remove(RBTNode<T>*& root, RBTNode<T>* node) {
RBTColor color = node->color;
RBTNode<T>* x;
RBTNode<T>* xfather = NULL;
if (node->left == NULL) {
x = node->right;
transplant(root, node, node->right);
} else if (node->right == NULL) {
x = node->left;
transplant(root, node, node->left);
} else {
RBTNode<T>* replace = node->right;
while (replace->left != NULL) replace = replace->left;
color = replace->color;
x = replace->right;
if (x == NULL) xfather = replace->parent;
if (replace->parent == node) {
if (x != NULL) x->parent = replace;
else xfather = replace;
// 相当于啥也没做,可以注释试一下结果
} else {
// 删除 replace 节点:replace 的右子树代替 replace
// replace right 指向 node 的 右儿子
// node 右儿子 指向 replace。
// 如果 replace 就是 node 的右儿子,
// 只需要 replace 代替 node 即可
transplant(root, replace, replace->right);
replace->right = node->right;
replace->right->parent = replace;
}
transplant(root, node, replace);
replace->left = node->left;
replace->left->parent = replace;
replace->color = node->color;
}
if (color == BLACK) {
if (x != NULL) removeFixUp(root, x);
else if (root != NULL) removeFixUp(root, xfather);
}
delete node;
return ;
}
// 删除修正
template<class T>
void RBTree<T>::removeFixUp(RBTNode<T>*&root, RBTNode<T>* node) {
RBTNode<T>* parent = rb_parent(node);
RBTNode<T>* bro;
while ( node && rb_parent(node) && rb_is_black(node)) {
parent = rb_parent(node);
if (parent->left == node) {
bro = parent->right;
if (rb_is_red(bro)) {
// case1 兄弟节点为红色
rb_set_black(bro);
rb_set_red(parent);
leftRotate(root, parent);
bro = parent->right;
}
if ( rb_is_black(bro)
&& (bro->left == NULL || rb_is_black(bro->left))
&& (bro->right == NULL || rb_is_black(bro->right)) ) {
// case2 兄弟节点黑色,其子节点全黑色
rb_set_red(bro);
// bro 兄弟节点不可能为空
// 因为当前节点是双黑
// 如果兄弟节点为空,则红黑树删除之前就是不平衡的
// 兄弟节点这边的路径少一个黑节点
node = parent;
parent = rb_parent(node);
} else {
if (bro->right == NULL || rb_is_black(bro->right)) {
// case3 兄弟黑,其右孩子黑
rb_set_black(bro->left);
rb_set_red(bro);
rightRotate(root, bro);
bro = parent->right;
}
rb_set_color(bro, parent->color);
rb_set_black(parent);
rb_set_black(bro->right);
leftRotate(root, parent);
node = root;
break;
}
} else {
bro = parent->left;
if (rb_is_red(bro)) {
// case1 兄弟节点为红色
rb_set_black(bro);
rb_set_red(parent);
leftRotate(root, parent);
bro = parent->left;
}
if ( rb_is_black(bro)
&& (bro->left == NULL || rb_is_black(bro->left))
&& (bro->right == NULL || rb_is_black(bro->right)) ) {
// case2 兄弟节点黑色,其子节点全黑色
rb_set_red(bro);
// bro 兄弟节点不可能为空
// 因为当前节点是双黑
// 如果兄弟节点为空,则红黑树删除之前就是不平衡的
// 兄弟节点这边的路径少一个黑节点
node = parent;
parent = rb_parent(node);
} else {
if (bro->left == NULL || rb_is_black(bro->left)) {
// case3 兄弟黑,其左孩子黑
rb_set_black(bro->right);
rb_set_red(bro);
leftRotate(root, bro);
bro = parent->left;
}
// case4 兄弟黑,其左孩子红,直接操作成平衡
rb_set_color(bro, parent->color);
rb_set_black(parent);
rb_set_black(bro->left);
rightRotate(root, parent);
node = root;
break;
}
}
}
if (node != NULL) {
rb_set_black(node);
}
}
// 查找外部接口
template<class T>
RBTNode<T>* RBTree<T>::search(T key) {
RBTNode<T>* res = search(_root, key);
return res;
}
// 查找内部实现
template<class T>
RBTNode<T>* RBTree<T>::search(RBTNode<T>* root, T key) const {
RBTNode<T>* x = root;
int res = _cmp(x->key, key);
while (x != NULL && res != 0) {
if (res > 0) {
x = x->right;
} else {
x = x->left;
}
if (x != NULL) {
res = _cmp(x->key, key);
} else {
return x;
}
}
return x;
}
// 外部接口,返回最小值
template<class T>
T RBTree<T>::minimum() {
RBTNode<T>* x = _root;
while (x->left != NULL) {
x = x->left;
}
return x->key;
}
// 外部接口返回最大值
template<class T>
T RBTree<T>::maximum() {
RBTNode<T>* x = _root;
while (x->right != NULL) {
x = x->right;
}
return x->key;
}
// 先序遍历内部实现
template <class T>
void RBTree<T>::preOrder(RBTNode<T>* tree) const
{
if(tree != NULL)
{
cout<< tree->key << " " ;
preOrder(tree->left);
preOrder(tree->right);
}
}
// 先序遍历外部接口
template<class T>
void RBTree<T>::preOrder() {
preOrder(_root);
}
// 中序遍历内部实现
template <class T>
void RBTree<T>::inOrder(RBTNode<T>* tree) const
{
if(tree != NULL)
{
inOrder(tree->left);
cout<< tree->key << " " ;
inOrder(tree->right);
}
}
// 中序遍历外部接口
template <class T>
void RBTree<T>::inOrder() {
inOrder(_root);
}
// 后序遍历内部实现
template <class T>
void RBTree<T>::postOrder(RBTNode<T>* tree) const
{
if(tree != NULL)
{
inOrder(tree->left);
inOrder(tree->right);
cout<< tree->key << " " ;
}
}
// 后序遍历外部接口
template <class T>
void RBTree<T>::postOrder() {
inOrder(_root);
}
// 打印的内部实现
template <class T>
void RBTree<T>::print(RBTNode<T>*& root, T key, int direction)
{
if(root != NULL)
{
if(direction == 0) {
cout << setw(4) << root->key << "(B) is root" << endl;
} else {
cout << setw(4) << root->key
<< (rb_is_red(root)?"(R)":"(B)") << " is "
<< setw(4) << key << "'s "
<< setw(14) << (direction==1?"right child" : "left child") << endl;
}
print(root->left, root->key, -1);
print(root->right,root->key, 1);
}
}
// 打印的外部接口
template <class T>
void RBTree<T>::print()
{
if (_root != NULL) {
print(_root, _root->key, 0);
} else {
cout << "INFO: the tree has nothing left !" << endl;
}
}
// test.cpp
#include <iostream>
#include "./RBTree.h"
using namespace std;
int main()
{
int a[] = { 10, 40, 30, 60, 90, 70, 20, 50, 80 };
int check_insert = 0; // "插入"动作的检测开关(0,关闭;1,打开)
int check_remove = 0; // "删除"动作的检测开关(0,关闭;1,打开)
int i;
int ilen = (sizeof(a)) / (sizeof(a[0]));
RBTree<int>* tree = new RBTree<int>(NULL);
cout << "== 原始数据: ";
for(i = 0; i < ilen; ++ i )
cout << a[i] <<" ";
cout << endl;
for(i = 0; i < ilen; ++ i )
{
tree->insert(a[i]);
// 设置check_insert=1,测试"添加函数"
if(check_insert)
{
cout << "== 添加节点: " << a[i] << endl;
cout << "== 树的详细信息: " << endl;
tree->print();
cout << endl;
}
}
cout << "== 前序遍历: ";
tree->preOrder();
cout << "\n== 中序遍历: ";
tree->inOrder();
cout << "\n== 后序遍历: ";
tree->postOrder();
cout << endl;
cout << "== 最小值: " << tree->minimum() << endl;
cout << "== 最大值: " << tree->maximum() << endl;
cout << "== 树的详细信息: " << endl;
tree->print();
// 设置check_remove=1,测试"删除函数"
if(check_remove)
{
for(i = 0; i < ilen; ++ i )
{
tree->remove(a[i]);
cout << "== 删除节点: " << a[i] << endl;
cout << "== 树的详细信息: " << endl;
tree->print();
cout << endl;
}
}
// 销毁红黑树
// tree->destroy();
return 0;
}
叶子节点判断方法:
- 左右是否为空
- 是否指向公共节点(因为叶子节点都是黑色的,所以只要指向定义的公共黑色节点即可)
左旋右旋操作(同 AVL 树的单向旋转,唯一不同的是需要旋转时维护指向父节点的指针)
void _left_rotate(rbtree* t, rbtree_node* x) {
auto y = x->right;
// 1. x 右子树改为 y 的左子树
x->right = y->left;
if (y->left != t->nil) {
y->left->parent = x;
}
// 2. y 的父节点变为 x 的父节点
y->parengt = x->parent;
if (x->parent == t->nil) {
// 是根节点
t->root = y;
} else if (x == x->parent->left){
y->parent->left = y;
} else {
y->parent->right = y;
}
// 3. 修改 x\y 的关系
y->left = x;
x->parent = y;
}