一、AVL树
什么是AVL树呢,其实就是普通平衡树加二叉搜索树的结合体,只不过该树的形状趋近于
完全二叉树甚至接近满二叉树。所以平衡性很好,很适合查找工作,但是不适合删除操作
删除的话可能会从最下面进行调平衡从下一直调到根节点,如果多次删除的话,那么效率
就会变得很低
AVL树的核心就在于调平衡,主要介绍调平衡的方法.
怎么区分LL型呢,从高往下数两个,从树最不平衡的那端往下数,
也就是先判断两边谁树高,这里可以看出左树最高,走左树,左树最高再走左树
所以就是LL型(右旋的样子,抽象一下,这样就可以降低左树的高度)
这里是水滴为子树
图片来源于《算法训练营》陈小玉著
这种类型需要先调平衡转换成LL,在调平衡
以上四种情况包含了所有的不平衡状态
下面是代码细节及其注释
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
template <class K>
class AVLNode {
public:
AVLNode() :lchild(nullptr), rchild(nullptr), height(1), data(K())
{}
AVLNode(K _data) :lchild(nullptr), rchild(nullptr), height(1), data(_data)
{}
K data;
int height;
AVLNode<K>* lchild;
AVLNode<K>* rchild;
};
template<class K>
class AVL
{
public:
AVL() :_root(nullptr)
{}
void clear(AVLNode<K>*& T)
{
if (!T) return;
clear(T->lchild);
clear(T->rchild);
delete T;
T = nullptr;
}
~AVL()
{
clear(_root);
}
void add(K x)
{
insert(_root, x);
}
void del(K x)
{
Delete(_root,x);
}
void Create1(AVLNode<K>*& T)
{
int n = 0;
int x = 0;
cin >> n;
for (int i = 0;i < n;i++)
{
x = rand();
insert(T, x);
}
}
void show()
{
inorder(_root);
}
private:
AVLNode<K>* _root;
//计算高度接口
int Height(AVLNode<K>* T)
{
if (!T) return 0;
return T->height;
}
//我们做模块化,耦合度低代码维护性高,复用性强
//更新当前节点的高度,要改变当前节点的值所以用引用
void updateHeight(AVLNode<K>*& T)
{
if (!T) return;//空节点
T->height = max(Height(T->lchild), Height(T->rchild)) + 1;
}
//写接口记得画一般情况的图
//左边高,右边低 && LL型 == 右旋
AVLNode<K>* LL_Rotate(AVLNode<K>*& T)//LL旋转,只是转一次,传引用是为了,修改当前节点的值
{
AVLNode<K>* tmp = T->lchild;//保存下当前节点左孩子,因为做根节点
T->lchild = tmp->rchild;//把空闲节点,挂接到当前节点的左孩子上,断开了当前节点和tmp的链接
tmp->rchild = T;//把tmp作为当前根节点
updateHeight(T);//更新T节点,往右旋之后只是改变了两个节点的高度,可以自己画图旋转看下
updateHeight(tmp);//更新tmp
return tmp;//返回当前更新过后的根节点
}
//左边低,右边高 && RR型 == 左旋
AVLNode<K>* RR_Rotate(AVLNode<K>*& T)
{
AVLNode<K>* tmp = T->rchild;//保存下当前节点右孩子,因为做根节点
T->rchild = tmp->lchild;//把空闲节点,挂接到当前节点的右孩子上,断开了当前节点和tmp的链接
tmp->lchild = T;//把tmp作为当前根节点
updateHeight(T);//更新T节点,往右旋之后只是改变了两个节点的高度,可以自己画图旋转看下
updateHeight(tmp);//更新tmp
return tmp;
}
//画图
//下面这两个旋转就可以复用左旋和右旋接口了
AVLNode<K>* LR_Rotate(AVLNode<K>*& T)
{
T->lchild = RR_Rotate(T->rchild);//先进行左旋变成LL型,然后调用LL型
//因为旋转后把返回值是根节点,所以链接到T->lchild下面
return LL_Rotate(T);
}
AVLNode<K>* RL_Rotate(AVLNode<K>* T)
{
T->rchild = LL_Rotate(T->lchild);
return RR_Rotate(T);
}
void adjust(AVLNode<K>*& T)
{
if (!T) return;//空树返回
if (Height(T->lchild) - Height(T->rchild) > 1)
//左子树高,沿着树高度大的那条下去进行判断
{
//然后再判断一下哪个高,判断出是LR类型,还是LL类型
if (Height(T->lchild->lchild) >= Height(T->lchild->rchild))
LL_Rotate(T);
else if (Height(T->lchild->rchild) > Height(T->lchild->lchild))
LR_Rotate(T);
}
else if (Height(T->rchild) - Height(T->lchild) > 1)
//右子树高,沿着树高度大的那条下去进行判断
{
//然后再判断一下哪个高, 判断出是LR类型,还是LL类型
if (Height(T->rchild->rchild) >= Height(T->rchild->rchild))
RR_Rotate(T);
else if (Height(T->rchild->lchild) > Height(T->lchild->lchild))
RL_Rotate(T);
}
}
AVLNode<K>* insert(AVLNode<K>*& T, K x)
{
if (!T)//树空,创建一个新节点
{
T = new AVLNode;
T->lchild = T->rchild = nullptr;
T->data = x;
T->height = 1;
return T;
}
if (T->data == x) return T;
if (x < T->data)//向左子树搜索插入
{
T->lchild = insert(T->lchild, x);
}
else if (x > T->data)
{
T->rchild = insert(T->rchild, x);
}
//插入过后更新
updateHeight(T);
adjust(T);
return T;
}
AVLNode<K>* Delete(AVLNode<K>*& T, int x)
{
if (!T) return T;
if (T->data == x)
{
if (!T->lchild || !T->rchild)//如果有一个孩子为空
//子承父业
{
AVLNode<K>* tmp = T;
T = (T->lchild) ? T->lchild : T->rchild;
delete tmp;
}
else
{
AVLNode<K>* tmp;
tmp = T->lchild;//最小的最大
//找前驱节点
while (tmp->rchild)
{
tmp = tmp->rchild;
}
//找到最左的最右的节点后,更新data
T->data = tmp->data;
T->lchild = Delete(T->lchild, T->data);//然后去左子树删除值为tmp->data的节点
}
}
else if (T->data > x)
{
T->lchild = Delete(T->lchild, x);//递归去左子树删除
}
else
{
T->rchild = Delete(T->rchild, x);//递归去右子树删除
}
updateHeight(T);//删掉一个之后要更新根节点T的高度
adjust(T);//然后调平衡,调平衡会更新高度
return T;
}
void inorder(AVLNode<K>* T)
{
if (T == nullptr) return;
inorder(T->lchild);
cout << T->data << " ";
inorder(T->rchild);
}
};
int main()
{
AVL<int> t;
t.add(4);
t.add(5);
return 0;
}
二,Treap(树堆)
树堆,顾明思异就是树+堆维护的平衡二叉搜索树
也有调平衡操作,跟AVL树调平衡类似,就不介绍了
Treap最主要的操作是随机值val,和key相结合
key是唯一的,val是唯一的(可能会相同),当节点中
存在着两个值的时候,那么就会生成一个唯一的节点,唯一的话
那么就话增加这颗树的平衡性,并且key维护二叉搜索树的性质
val(随机值)维护堆的性质,那么这样就保证了该树趋近于平衡,虽然
不如AVL树平衡和红黑树,但是他的删除效率比AVL树和红黑树都要
来的高效,因为他保证平衡性不需要维护很多性质,只要用两个值维护好堆的性质
和二叉搜索树的性质即可,所以删除和插入很高效,但是由于随机的值存在
稳定性较差
模板题,题目摘自《算法进阶指南》
以下是代码,具体一些代码细节在注释里
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
const int N = 110000,INF = 1e8;
typedef long long LL;
int n;
struct node
{
int l,r;
int key,val;//节点键值key维护BST,val维护堆的性质,val越随机越好
int cnt,size;//cnt代表这个数出现的次数,size代表当前节点子树里的节点个数
}tr[N];
int root,idx;
void pushup(int p)//儿子信息更新父节点信息
{
tr[p].size = tr[tr[p].l].size + tr[tr[p].r].size + tr[p].cnt;
}
int get_node(int key)
{
tr[++idx].key = key;
tr[idx].val = rand();
tr[idx].cnt = 1;
tr[idx].size = 1;
return idx;
}
//treap树最关键的操作,左旋和右旋
void zig(int& p)//右旋
{
int q = tr[p].l;//先把p的左儿子存下来
tr[p].l = tr[q].r;//p的左儿子是q的右儿子
tr[q].r = p;
p = q;
pushup(p),pushup(tr[p].r);
}
void zag(int& p)//左旋
{
int q = tr[p].r;
tr[p].r = tr[q].l;
tr[q].l = p;
p = q;
pushup(p),pushup(tr[p].l);
}
void bulid()
{
get_node(-INF);
get_node(INF);
root = 1,tr[1].r = 2;//根节点是1号,1号的右节点是2号
pushup(root);
if(tr[1].val < tr[2].val) zag(root);
}
void insert(int& p,int key)
{
if(!p) p = get_node(key);//如果到达叶子节点,那么创建节点并把
else if(tr[p].key == key) tr[p].cnt++;
else if(tr[p].key > key)
{
insert(tr[p].l,key);
//往左递归,比较父节点的左孩子
if(tr[tr[p].l].val > tr[p].val) zig(p);//孩子节点
}
//维护val是根节点root.val > 子树 root.l.val or root.r.val
else
{
insert(tr[p].r,key);
if(tr[tr[p].r].val < tr[p].val) zag(p);
}
pushup(p);//不是右旋的话也要统计,到达叶节点统计一下
}
void remove(int& p,int key)
{
if(!p) return;//如果删除的值不存在,返回
else if(tr[p].key == key)//找到要删除的值
{
//要删除的话有三种情况
//1.cnt > 1
//2.cnt == 1,该点删除的话还需要判断是否是叶子节点
//3.删除的节点就是叶子节点
if(tr[p].cnt > 1) tr[p].cnt--;//如果该值的cnt大于1,说明有,减去一个cnt就行
else if(tr[p].l || tr[p].r)//这里的情况可以把cnt == 1的情况给包括了
{//判断是否有叶子节点,有叶子节点就执行
//如果只有右节点,判断一下,左旋
//如果只有左节点,判断一下,右旋
if(!tr[p].r || tr[tr[p].r].val < tr[tr[p].l].val)
//没有右节点,或者右孩子的val小于左孩子的val
{
zig(p);
remove(tr[p].r,key);
}
else
{
zag(p);//左旋
remove(tr[p].l,key);
}
}
else p = 0;
}
else if(tr[p].key > key) remove(tr[p].l,key);
else if(tr[p].key < key) remove(tr[p].r,key);
pushup(p);
}
int get_rank_by_key(int p,int key)//通过数值找排名
{
if(!p) return -INF;//如果没有该值,删除
if(tr[p].key == key) return tr[tr[p].l].size + 1;
if(tr[p].key > key) return get_rank_by_key(tr[p].l,key);
return tr[tr[p].l].size + tr[p].cnt + get_rank_by_key(tr[p].r,key);
}
int get_key_by_rank(int p,int rank)//通过排名找数值 传p而不是传&p
{
if(!p) return -INF;
if(tr[tr[p].l].size >= rank) return get_key_by_rank(tr[p].l,rank);
if(tr[tr[p].l].size + tr[p].cnt >= rank) return tr[p].key;//左子树+当前节点个数>=rank,则就是当前节点
//因为前面执行过来不是大于等于rank,而加了之后才>=rank说明当前这个数就是key
return get_key_by_rank(tr[p].r,rank-tr[tr[p].l].size-tr[p].cnt);//-tr[p].cnt,递归右子树,找到
//排名等于rank-tr[tr[p].l].size-tr[p].cnt
}
int get_prev(int p,int key)//找到前驱节点,最小值最大,比key小的最大值
{
if(!p) return -INF;
if(tr[p].key>=key) return get_prev(tr[p].l,key);//当前这个数>=key 则要找的数在当前节点的左子树里
return max(tr[p].key, get_prev(tr[p].r, key));//小于的话,说明找到了,每次拿比较过后
//的max值去返回就会的得到最小值最大
}
int get_next(int p,int key)//找到后继节点,最大值最小,比key大的最小值
{
if (!p) return INF;
if (tr[p].key <= key) return get_next(tr[p].r, key);
return min(tr[p].key, get_next(tr[p].l, key));
}
int main()
{
cin>>n;
while(n--)
{
int op,x;
cin>>op>>x;
LL ans;
if(op == 1) insert(root,x);
else if(op == 2) remove(root,x);
else if(op == 3)
{
ans = get_rank_by_key(root,x);
cout<<ans<<endl;
}
else if(op == 4)
{
ans = get_key_by_rank(root,x);
cout<<ans<<endl;
}
else if(op == 5)
{
ans = get_prev(root,x);
cout<<ans<<endl;
}
else if(op == 6)
{
ans = get_next(root,x);
cout<<ans<<endl;
}
}
return 0;
}
三、红黑树
红黑树讲解比较麻烦,以下的gpt对我实现的代码的总结
Red-Black Tree 通过如下的方式进行自平衡:
节点是红色或黑色。
根节点是黑色。
所有叶子节点(NIL节点,空节点)是黑色。
每个红色节点的两个子节点都是黑色(从每个叶子到根的所有路径上不能有两个连续的红色节点)。
从任一节点到其每个叶子的所有路径包含相同数目黑色节点。
具体实现时,我们可以定义 Node 类来表示红黑树的节点,其中包括节点的键、值、颜色等属性。具体的实现需要考虑以下情况:
插入节点
在插入节点时,我们需要考虑节点的颜色和位置,以保证插入后仍然满足红黑树的性质。以下是插入节点的具体实现细节:
插入到空树
当插入到空树时,我们需要创建一个根节点,将其颜色设置为黑色,并将新节点插入作为根节点的左孩子或右孩子。
插入到非空树
首先通过二叉搜索树的插入方式找到新节点要插入的位置,然后将新节点插入到该位置,并将其颜色设置为红色。接下来需要保证红黑树的性质,具体实现如下:
如果新节点的父节点为黑色,则满足红黑树的性质,无需处理。
如果新节点的父节点为红色,则需要进行变色或旋转操作,以满足红黑树的性质。
为了方便后续实现,以下称新插入的节点为 X,X 的父节点为 P,X 的祖父节点为 G,X 的叔父节点为 U。具体的情况如下:
Case 1:如果 P 为根节点,则将其颜色设置为黑色。
Case 2:如果 P 的颜色为黑色,则满足红黑树的性质,无需处理。
Case 3:如果 P 和 U 都为红色,则将 P 和 U 的颜色都设置为黑色,G 的颜色设置为红色,然后将 X 的位置设置为 G,继续向上判断。
Case 4:如果 P 为红色,U 为黑色,并且 X 是 P 的右孩子而且 P 是 G 的左孩子,或者 X 是 P 的左孩子而且 P 是 G 的右孩子,则需要进行旋转操作。具体操作如下:
如果 X 是 P 的右孩子,将 X 和 P 交换,得到情况 "X 是 P 的左孩子,P 是 G 的左孩子"。
将 P 向左旋转,得到情况 "X 是 P 的左孩子,P 是 G 的左孩子,X 的兄弟节点为 P"。
将 P 和 G 的颜色互换,得到最终的结果。
Case 5:如果 P 为红色,U 为黑色,并且 X 是 P 的左孩子而且 P 是 G 的左孩子,或者 X 是 P 的右孩子而且 P 是 G 的右孩子,则需要进行旋转操作。具体操作如下:
将 P 和 G 的颜色互换,得到情况 "X 是 P 的右孩子,P 是 G 的右孩子"。
将 P 向右旋转,得到情况 "X 是 P 的右孩子,P 是 G 的右孩子,X 的兄弟节点为 P"。
将 P 和 G 的颜色互换,得到最终的结果。
删除节点
在删除节点时,我们需要考虑节点的颜色和位置,以保证删除后仍然满足红黑树的性质。以下是删除节点的具体实现细节:
删除叶子节点
直接删除该节点,并将其父节点指向 NULL。
删除有一个子节点的节点
将该节点的子节点接到其父节点上,并删除该节点。
删除有两个子节点的节点
在右子树中找到最小的节点,将其值和键复制到要删除的节点,然后删除右子树中的最小节点。
在删除节点时,如果节点的颜色为黑色,还需要进一步处理,以保证红黑树的性质。如果删除的节点是叶子节点或只有一个子节点的节点,删除时并不会破坏红黑树的性质。如果删除的节点有两个子节点,需要找到其后继节点(即右子树中的最小节点),然后用后继节点替换要删除的节点,此时后继节点的颜色可能是红色或黑色,需要进行进一步处理,具体实现如下:
Case 1:如果后继节点是红色,则将其颜色设置为黑色。
Case 2:如果后继节点是黑色,并且其任一子节点是红色,则将其子节点的颜色设置为黑色,并将后继节点的颜色设置为红色。
Case 3:如果后继节点是黑色,并且其子节点都是黑色,则需要进行变色或旋转操作,以满足红黑树的性质。
为了方便后续实现,以下称要删除的节点为 X,X 的父节点为 P,X 的兄弟节点为 S,S 的子节点为 S1 和 S2,S1 或 S2 和 S 的颜色为红色。具体的情况如下:
Case 1:如果 X 是根节点,则将其颜色设置为黑色。
Case 2:如果 X 的兄弟节点 S 是红色,则将 S 的颜色设置为黑色,P 的颜色设置为红色,然后对 P 进行旋转操作,使 X 的兄弟节点为黑色。
Case 3:如果 X、X 的父节点 P 和 X 的兄弟节点 S 都是黑色,并且 S 的子节点都是黑色,则将 S 的颜色设置为红色,将 X 的位置设置为 P,继续向上判断。
Case 4:如果 X 的兄弟节点 S 是黑色,S 的左孩子 S1 是红色,S 的右孩子 S2 是黑色,则进行以下操作:
将 S 和 S1 的颜色互换,得到情况 "X 的兄弟节点 S 的右孩子 S2 是红色"。
将 S 向右旋转,将 S2 和 P 的颜色也进行相应的变换,得到情况 "X 的兄弟节点 S 的左孩子 S1 是黑色,S 的右孩子 S2 是红色"。
重新设置 S、S1 和 S2 的颜色,得到最终的结果。
Case 5:如果 X 的兄弟节点 S 是黑色,S 的右孩子 S2 是红色,则进行以下操作:
将 S 和 S2 的颜色互换,得到情况 "X 的兄弟节点 S 的左孩子 S1 是黑色"。
将 S 向左旋转,将 S1 和 P 的颜色也进行相应的变换,得到情况 "X 的兄弟节点 S 的左孩子 S1 是红色,S 的右孩子 S2 是黑色"。
重新设置 S、S1 和 S2 的颜色,得到最终的结果。
Case 6:如果 X 的兄弟节点 S 是黑色,S 的左孩子 S1 是红色,则进行以下操作:
将 S 和 S1 的颜色互换,得到情况 "X 的兄弟节点 S 的右孩子 S2 是黑色"。
将 S 向右旋转,将 S2 和 P 的颜色也进行相应的变换,得到情况 "X 的兄弟节点 S 的左孩子 S1 是黑色,S 的右孩子 S2 是红色"。
重新设置 S、S1 和 S2 的颜色,得到最终的结果。
以上就是 Red-Black Tree 的实现细节和各种情况的判断,希望能对您有所帮助。
以下是代码及其一些细节
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
// 红黑树节点的颜色,红色表示节点为2节点(即2-3树中的3节点中的左子节点或者右子节点),黑色表示节点为3节点或空节点
enum Color {
RED, BLACK
};
// 红黑树节点的类
class RBNode {
public:
int key; // 节点的键
int value; // 节点的值
Color color; // 节点的颜色,红色或者黑色
RBNode* left; // 节点的左子节点,可以是红节点或者黑节点
RBNode* right; // 节点的右子节点,可以是红节点或者黑节点
// 构造函数
RBNode(int key, int value, Color color) :
key(key), value(value), color(color), left(nullptr), right(nullptr) {}
};
// 红黑树的类
class RBTree {
private:
RBNode* root;
public:
// 构造函数
RBTree() : root(nullptr) {}
// 插入操作
void insert(int key, int value) {
root = insert_node(root, key, value); // 调用私有的插入节点函数
root->color = BLACK; // 根节点必须为黑色
}
// 查找操作
int search(int key) {
RBNode* node = search_node(root, key); // 调用私有的查找节点函数
if (node != nullptr) {
return node->value;
}
else {
return -1;
}
}
// 删除操作
void erase(int key) {
root = erase_node(root, key); // 调用私有的删除节点函数
if (root != nullptr) {
root->color = BLACK; // 根节点必须为黑色
}
}
// 查询最大键值
int max() {
RBNode* node = max_node(root);
if (node != nullptr) {
return node->key;
}
else {
return -1;
}
}
// 查询最小键值
int min() {
RBNode* node = min_node(root);
if (node != nullptr) {
return node->key;
}
else {
return -1;
}
}
private:
// 左旋操作
RBNode* left_rotate(RBNode* node) {
RBNode* right_child = node->right; // 获取需要左旋的节点的右子节点
node->right = right_child->left; // 右子节点的左子节点变成需要左旋的节点的右子节点
right_child->left = node; // 需要左旋的节点变成右子节点的左子节点
right_child->color = node->color; // 右子节点颜色变成需要左旋的节点的颜色
node->color = RED; // 需要左旋的节点变成红色
return right_child; // 返回左旋后的根节点
}
// 右旋操作
RBNode* right_rotate(RBNode* node) {
RBNode* left_child = node->left; // 获取需要右旋的节点的左子节点
node->left = left_child->right; // 左子节点的右子节点变成需要右旋的节点的左子节点
left_child->right = node; // 需要右旋的节点变成左子节点的右子节点
left_child->color = node->color; // 左子节点颜色变成需要右旋的节点的颜色
node->color = RED; // 需要右旋的节点变成红色
return left_child; // 返回右旋后的根节点
}
// 颜色翻转操作
void flip_color(RBNode* node) {
node->color = RED; // 需要翻转颜色的节点变成红色
node->left->color = BLACK; // 左子节点和右子节点变成黑色
node->right->color = BLACK;
}
// 插入节点
RBNode* insert_node(RBNode* node, int key, int value) {
if (node == nullptr) {
return new RBNode(key, value, RED); // 如果节点是空节点,则创建一个红色节点
}
if (key < node->key) {
node->left = insert_node(node->left, key, value); // 插入到左子树
}
else if (key > node->key) {
node->right = insert_node(node->right, key, value); // 插入到右子树
}
else {
node->value = value; // 如果节点已经存在,则更新节点的值
}
// 实施红黑树的正常插入操作
if (is_red(node->right) && !is_red(node->left)) { // 如果需要右旋
node = left_rotate(node);
}
if (is_red(node->left) && is_red(node->left->left)) { // 如果需要左旋
node = right_rotate(node);
}
if (is_red(node->left) && is_red(node->right)) { // 如果需要颜色翻转
flip_color(node);
}
return node;
}
// 删除节点
RBNode* erase_node(RBNode* node, int key) {
if (node == nullptr) {
return nullptr;
}
if (key < node->key) {
node->left = erase_node(node->left, key); // 删除左子树的节点
}
else if (key > node->key) {
node->right = erase_node(node->right, key); // 删除右子树的节点
}
else {
if (node->left == nullptr) {
RBNode* right_child = node->right;
delete node;
return right_child;
}
if (node->right == nullptr) {
RBNode* left_child = node->left;
delete node;
return left_child;
}
RBNode* replace_node = min_node(node->right); // 找到右子树中的最小节点
node->key = replace_node->key; // 用最小节点替换需要删除的节点
node->value = replace_node->value;
node->right = erase_min(node->right); // 删除替换节点,即删除右子树中的最小节点
}
// 实施红黑树的删除后的修正操作
if (is_red(node->right) && !is_red(node->left)) { // 如果需要右旋和颜色翻转
node = left_rotate(node);
}
if (is_red(node->left) && is_red(node->left->left)) { // 如果需要左旋和颜色翻转
node = right_rotate(node);
}
if (is_red(node->left) && is_red(node->right)) { // 如果需要颜色翻转
flip_color(node);
}
return node;
}
// 删除最小键值节点
RBNode* erase_min(RBNode* node) {
if (node->left == nullptr) {
RBNode* right_child = node->right;
delete node;
return right_child;
}
if (!is_red(node->left) && !is_red(node->left->left)) { // 如果左子节点为黑色且左子节点的左子节点为黑色,则进行颜色翻转
node = move_red_left(node);
}
node->left = erase_min(node->left); // 递归删除左子树的最小键值节点
return balance(node); // 做一次平衡操作
}
// 将左子树的红节点移到右子树中
RBNode* move_red_left(RBNode* node) {
flip_color(node); // 颜色翻转
if (is_red(node->right->left)) { // 如果右子节点的左子节点为红色,则右旋
node->right = right_rotate(node->right);
node = left_rotate(node);
flip_color(node);
}
return node;
}
// 查询节点
RBNode* search_node(RBNode* node, int key) {
if (node == nullptr) {
return nullptr; // 节点不存在,则返回空指针
}
if (key < node->key) {
return search_node(node->left, key); // 继续在左子树中查找
}
else if (key > node->key) {
return search_node(node->right, key);// 继续在右子树中查找
}
else {
return node; // 找到节点,则返回指向该节点的指针
}
}
// 查询最大节点
RBNode* max_node(RBNode* node) {
if (node->right == nullptr) { // 如果节点的右子树为空,则该节点为最大节点
return node;
}
else {
return max_node(node->right); // 继续在右子树中查找
}
}
// 查询最小节点
RBNode* min_node(RBNode* node) {
if (node->left == nullptr) { // 如果节点的左子树为空,则该节点为最小节点
return node;
}
else {
return min_node(node->left); // 继续在左子树中查找
}
}
// 平衡操作,保证每个节点左右子树中黑节点的数量相等
RBNode* balance(RBNode* node) {
if (is_red(node->right)) { // 如果右子节点为红色,则左旋
node = left_rotate(node);
}
if (is_red(node->left) && is_red(node->left->left)) { // 如果左子节点为红色且左子节点的左子节点为红色,则右旋
node = right_rotate(node);
}
if (is_red(node->left) && is_red(node->right)) { // 如果左右子节点都为红色,则进行颜色翻转
flip_color(node);
}
return node; // 返回平衡后的根节点
}
// 判断节点的颜色是否为红色,如果节点为nullptr,则其颜色为黑色
bool is_red(RBNode* node) {
return (node != nullptr && node->color == RED);
}
};
int main() {
RBTree tree;
tree.insert(3, 5); // 插入键值为3,值为5的节点
tree.insert(1, 10); // 插入键值为1,值为10的节点
tree.insert(5, 7); // 插入键值为5,值为7的节点
cout << tree.search(3) << endl; // 查询键值为3的节点的值,输出5
cout << tree.search(2) << endl; // 查询键值为2的节点的值,输出-1,表示未找到
cout << tree.max() << endl; // 查询红黑树中最大的键的值,输出5
cout << tree.min() << endl; // 查询红黑树中最小的键的值
}