1.首先介绍一下什么是BST(二叉查找树)
- 若其左子树非空,则左子树上所有节点的值都小于根节点的值
- 若其右子树非空,则右子树上所有节点的值都大于根节点的值
- 其左右子树都是一棵二叉查找树
- 二叉排序树通过中序遍历可以得到递增序列
如下:
把二叉查找树投影下来(中序遍历)就是从小到大排序的数组:1 2 3 4 6 7 9。
那么为什么说BST不行呢?
一个数据结构好不好,主要看它的增删改查的效率。
先来看看BST的查找效率:找上图中的7。
1. 7 > 3 看右子树
2. 7 > 6 看右子树
3. 7 < 9 看左子树
4. 7 = 7 找到了
这种方式正是二分查找的思想,查找所需的最大次数=二叉查找的高度。
删除和修改本质上也是查找,需要 O(logn) 时间。
再来看看BST的插入:
1.假设初始的二叉查找树只有三个节点,根节点值为9,左孩子值为8,右孩子值为12:
2.接下来我们依次插入如下五个节点:7,6,5,4,3。依照二叉查找树的特性,结果会变成什么样呢?
笑死了,这效率和线性的没区别!而这种情况正是BST的缺点。
2.让我们欢迎本文的主角红黑树!
红黑树是一种自平衡的BST,它是由 Rudolf Bayer 于1978年发明,除了符合BST的基本特性外,还具有以下的特性:
1.节点是红色或黑色。
2.根节点是黑色。
3.每个叶子节点都是黑色的空节点(NIL节点)。
4 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
5.从任一节点到其每个叶子(NIL节点)的所有路径都包含相同数目的黑色节点。(区分是否是红黑树的重点)
规则很多,正是因为规则的限定,才保证了红黑树的自平衡。红黑树从根到叶的最长路径不会超过最短路径的2倍。
注意:有些文章写的是NULL节点,是不对的,NIL与NULL有区别!
一、概念不同
Nil:表示无值,任何变量在没有被赋值之前的值都为nil。
Null:是在计算中具有保留的值。
二、功能不同
Nil:用于区别其他任何值。
Null:用于指示指针不引用有效对象。
三、针对不同
Nil:针对对象,而空对象不是说不占用空间,相当于一个“洗白”,回到初始状态。
Null:针对指针,对对象指针和非对象指针都有效,Null不会占用空间。
如下:
红黑树主要是处理BST的缺点,不过它所有的操作都有点复杂,但是为了效率没办法。
当红黑树执行插入或者删除操作的时候,红黑树的规则可能被打破,这时候就需要做出一些调整来让红黑树保持平衡。
如下操作:
1.向原红黑树插入值为14的新节点:
由于父节点15是黑色节点,因此这种情况并不会破坏红黑树的规则,无需做任何调整。
2.向原红黑树插入值为21的新节点:
由于父节点22是红色节点,因此这种情况打破了红黑树的规则4(每个红色节点的两个子节点都是黑色),必须进行调整,使之重新符合红黑树的规则。
注意为什么新插入的节点是红色?
如果插入节点是黑色,就一定会违背每条查找线上黑色节点个数一致的规则,插入红色,就可能不需要变色或者旋转,所以待插入节点都是红色。
以上问题正是红黑树解决的重点:
解决方法有两种,变色和旋转,旋转分为左旋转和右旋转。
变色:
红黑树是一种平衡的二叉搜索树,它的节点在存储了普通二叉搜索树节点的值和指向左右子节点的指针外,还增加了表示节点颜色的标记,标记可能是红色或者黑色。在红黑树的操作中,经常会用到节点变色的操作,其原理如下:
1.节点变色的本质是改变节点的颜色标记,将红色节点变为黑色,或将黑色节点变为红色。
2.节点变色的前提条件是该节点与其父节点、子节点的颜色关系满足特定条件,即:(1)不存在两个相邻的红色节点,也就是红色节点的父节点和子节点都是黑色。
(2)根节点必须为黑色。
3.节点变色有两种情况:
(1)红色节点变为黑色。这种情况通常发生在删除操作中,删除红色节点必须保证其子节点为红色,所以删除红色节点后会把其子节点的颜色改为黑色。
(2)黑色节点变为红色。这种情况通常发生在红黑树的平衡调整操作中,如果节点的父节点为黑色,则改为红色后不会影响红黑树的平衡性质,而更改后的颜色有助于平衡调整中旋转操作的实现。
旋转:
红黑树旋转操作是对树进行结构调整的一种操作,主要有左旋和右旋两种。
左旋:将一个节点向左下方移动,并使其成为一个左子节点,同时其右子节点成为新的父节点。左旋的本质是将当前节点的右子树中的某个节点上移到当前节点度上(即作为当前节点的父节点)。
右旋:将一个节点向右上方移动,并使其成为一个右子节点,同时其左子节点成为新的父节点。右旋的本质是将当前节点的左子树中的某个节点上移到当前节点度上(即作为当前节点的父节点)。
左旋和右旋操作都是为了保持红黑树的平衡性,即保持任何节点的左右子树高度相等或相差不超过1,从而实现快速的查找、插入和删除操作。
具体来说,红黑树和AVL树不同之处在于其左右子树的高度可以相差不超过1,因此旋转是随时发生的。当某个节点的左子树高度大于右子树高度时,需要对该节点进行右旋操作;当某个节点的右子树高度大于左子树高度时,需要对该节点进行左旋操作。这样可以不断地调整树的结构,使其保持平衡,并且不会破坏红黑树的特性。
以上是文字叙述,比较难懂,我这里给个B站讲的最清楚的视频链接:谢某人er的个人空间_哔哩哔哩_bilibili
这位大哥讲的不错,红黑树重在搞懂原理!
以下是红黑树的C++实现(包含插入、删除、查找、打印等操作):
#include <iostream>
using namespace std;
// 红黑树节点颜色
enum Color {
RED,
BLACK
};
// 红黑树节点结构体
struct RBTNode {
int key;
Color color;
RBTNode *left, *right, *parent;
RBTNode(int k, Color c, RBTNode *l, RBTNode *r, RBTNode *p) : key(k), color(c), left(l), right(r), parent(p) {}
};
// 红黑树类
class RBTree {
private:
RBTNode *root; // 红黑树根节点
/**
* 左旋
* @param x 转轴节点
*/
void leftRotate(RBTNode *x) {
RBTNode *y = x->right;
x->right = y->left;
if (y->left != NULL)
y->left->parent = x;
y->parent = x->parent;
if (x->parent == NULL)
root = y;
else if (x == x->parent->left)
x->parent->left = y;
else
x->parent->right = y;
y->left = x;
x->parent = y;
}
/**
* 右旋
* @param x 转轴节点
*/
void rightRotate(RBTNode *x) {
RBTNode *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 == x->parent->left)
x->parent->left = y;
else
x->parent->right = y;
y->right = x;
x->parent = y;
}
/**
* 红黑树插入调整
* @param z 新插入的节点
*/
void insertFixup(RBTNode *z) {
while (z != root && z->parent->color == RED) {
if (z->parent == z->parent->parent->left) {
RBTNode *y = z->parent->parent->right;
if (y != NULL && y->color == RED) {
z->parent->color = BLACK;
y->color = BLACK;
z->parent->parent->color = RED;
z = z->parent->parent;
} else {
if (z == z->parent->right) {
z = z->parent;
leftRotate(z);
}
z->parent->color = BLACK;
z->parent->parent->color = RED;
rightRotate(z->parent->parent);
}
} else {
RBTNode *y = z->parent->parent->left;
if (y != NULL && y->color == RED) {
z->parent->color = BLACK;
y->color = BLACK;
z->parent->parent->color = RED;
z = z->parent->parent;
} else {
if (z == z->parent->left) {
z = z->parent;
rightRotate(z);
}
z->parent->color = BLACK;
z->parent->parent->color = RED;
leftRotate(z->parent->parent);
}
}
}
root->color = BLACK;
}
/**
* 红黑树删除调整
* @param x 被删除的节点
*/
void deleteFixup(RBTNode *x) {
while (x != root && x->color == BLACK) {
if (x == x->parent->left) {
RBTNode *w = x->parent->right;
if (w->color == RED) {
w->color = BLACK;
x->parent->color = RED;
leftRotate(x->parent);
w = x->parent->right;
}
if ((w->left == NULL || w->left->color == BLACK) && (w->right == NULL || w->right->color == BLACK)) {
w->color = RED;
x = x->parent;
} else {
if (w->right == NULL || w->right->color == BLACK) {
w->left->color = BLACK;
w->color = RED;
rightRotate(w);
w = x->parent->right;
}
w->color = x->parent->color;
x->parent->color = BLACK;
w->right->color = BLACK;
leftRotate(x->parent);
x = root;
}
} else {
RBTNode *w = x->parent->left;
if (w->color == RED) {
w->color = BLACK;
x->parent->color = RED;
rightRotate(x->parent);
w = x->parent->left;
}
if ((w->left == NULL || w->left->color == BLACK) && (w->right == NULL || w->right->color == BLACK)) {
w->color = RED;
x = x->parent;
} else {
if (w->left == NULL || w->left->color == BLACK) {
w->right->color = BLACK;
w->color = RED;
leftRotate(w);
w = x->parent->left;
}
w->color = x->parent->color;
x->parent->color = BLACK;
w->left->color = BLACK;
rightRotate(x->parent);
x = root;
}
}
}
x->color = BLACK;
}
/**
* 查找最小值节点
* @param node 起始节点
* @return 最小值节点
*/
RBTNode *findMinNode(RBTNode *node) {
while (node->left != NULL)
node = node->left;
return node;
}
/**
* 中序遍历打印红黑树
* @param node 起始节点
*/
void inorderPrint(RBTNode *node) {
if (node != NULL) {
inorderPrint(node->left);
cout << node->key << " ";
inorderPrint(node->right);
}
}
public:
// 构造函数
RBTree() {
root = NULL;
}
// 插入节点
void insert(int key) {
RBTNode *z = new RBTNode(key, RED, NULL, NULL, NULL);
RBTNode *x = root;
RBTNode *y = NULL;
while (x != NULL) {
y = x;
if (z->key < x->key)
x = x->left;
else if (z->key > x->key)
x = x->right;
else {
// 在红黑树中不允许存在相同关键字的节点
cout << "Error: key " << key << " already exists in RBTree." << endl;
return;
}
}
z->parent = y;
if (y == NULL)
root = z; // 红黑树为空
else if (z->key < y->key)
y->left = z;
else
y->right = z;
insertFixup(z);
}
// 删除节点
void remove(int key) {
RBTNode *z = root;
while (z != NULL) {
if (z->key == key)
break;
else if (key < z->key)
z = z->left;
else
z = z->right;
}
if (z == NULL) {
cout << "Error: key " << key << " does not exist in RBTree." << endl;
return;
}
RBTNode *y = z;
Color yOriginalColor = y->color;
RBTNode *x;
if (z->left == NULL) {
x = z->right;
transplant(z, z->right);
} else if (z->right == NULL) {
x = z->left;
transplant(z, z->left);
} else {
y = findMinNode(z->right);
yOriginalColor = y->color;
x = y->right;
if (y->parent == z)
x->parent = y;
else {
transplant(y, y->right);
y->right = z->right;
y->right->parent = y;
}
transplant(z, y);
y->left = z->left;
y->left->parent = y;
y->color = z->color;
}
if (yOriginalColor == BLACK)
deleteFixup(x);
delete z;
}
// 查找节点
RBTNode *find(int key) {
RBTNode *node = root;
while (node != NULL) {
if (node->key == key)
return node;
else if (key < node->key)
node = node->left;
else
node = node->right;
}
return NULL;
}
// 打印红黑树
void print() {
inorderPrint(root);
cout << endl;
}
private:
/**
* 帮助函数:替换节点
* @param u 原节点
* @param v 新节点
*/
void transplant(RBTNode *u, RBTNode *v) {
if (u->parent == NULL)
root = v;
else if (u == u->parent->left)
u->parent->left = v;
else
u->parent->right = v;
if (v != NULL)
v->parent = u->parent;
}
};
测试示例:
int main() {
RBTree T;
T.insert(41);
T.insert(38);
T.insert(31);
T.insert(12);
T.insert(19);
T.insert(8);
T.insert(60);
T.insert(51);
T.insert(65);
T.print(); // 8 12 19 31 38 41 51 60 65
T.remove(31);
T.print(); // 8 12 19 38 41 51 60 65
return 0;
}
屏幕前的你懂了吗?