AVL树,Treap树,红黑树的实现

3 篇文章 0 订阅
2 篇文章 0 订阅

一、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;      // 查询红黑树中最小的键的值
}
  • 12
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

乖的小肥羊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值