红黑树概念及应用(c++)

平衡树

二叉搜索树

或者是一棵空树,或者是具有下列性质的二叉树:若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;它的左、右子树也分别为二叉排序树。

但是,一个不具备平衡性的查找树可能退化为单链表,时间复杂度退化为O(N)。
查找树退化
二叉搜索树不具有自平衡的概念。

平衡二叉树

它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
自平衡的二叉树平衡二叉树保证了在最差的情况下,二叉树依然能够保持绝对的平衡,即左右两个子树的高度差的绝对值不超过1。但是这又会带来一个问题,那就是平衡二叉树的定义过于严格,导致每次插入或者删除一个元素之后,都要去维护二叉树整体的平衡,这样产生额外的代价又太大了。二叉搜索树可能退化成链表,而平衡二叉树维护平衡的代价开销又太大了。

红黑树

红黑树的本质其实也是对概念模型:2-3-4树的一种实现。2-3-4树是阶数为4的B树,全称为BalanceTree,平衡树。这种结构主要用于查找,最终要的特性是平衡,能够在最坏的情况下也保持O(LogN)的时间复杂度实现查找1

**红黑树是一种含有红黑结点并能自平衡的二叉查找树。**它必须除了满足二叉搜索树的性质外,还要满足下面的性质:
性质1:每个节点要么是黑色,要么是红色。
性质2:根节点是黑色。
性质3:每个叶子节点(NIL)是黑色。
性质4:每个红色结点的两个子结点一定都是黑色。
性质5:任意一结点到每个叶子结点的路径都包含数量相同的黑结点
性质的详细讲解,参照大佬的文章2

使用场景

红黑树的使用主要是基于两个特点:
1. 查找效率O(logn),适合用于key->value的查找
2. 中序遍历是顺序的。
红黑树经典应用:
1. map,利用性质1,将key作为索引存入红黑树,通过key可以查找到value。
2. nginx中用于定时器等,利用特点2,将定时任务到期的timetamp存入红黑树,通过中序遍历,查找到红黑树中最小的节点,急最近要到期的定时任务。
3. cfs(Completely Fair Scheduler,完全调用调度),利用特点2,将进程调度的时间存入红黑树,查找最小的节点,即调度时间最短的进程,进行调度,可以做到公平调度。
4. 内存管理,利用特点1,可以快速查找到对应的内存块。
内存管理的红黑树的key是内存首地址?
a. 首地址+长度
b. 首地址+尾地址

红黑树实现

定义节点和红黑树

typedef int KEY_TYPE;

#define RED 0
#define BLACK 1

int key_compare(KEY_TYPE a,KEY_TYPE b){

}//遗留此函数 就可做模板

typedef struct _rbtree_node{

    unsigned char color;
    struct _rbtree_node *left;
    struct _rbtree_node *right;
    struct _rbtree_node *parent;
    
    KEY_TYPE key;
    void *value; //万能指针
}rbtree_node;

typedef struct _rbtree{
    rbtree_node *root;
    rbtree_node *nil;//叶子节点为黑,所以定义一个专用叶子节点

}rbtree;

左旋和右旋

左旋和右旋用于插入新节点后,继续符合红黑树的条件。

左旋右旋
旋转步骤
需要动三根线,六个指针。

void _left_rotate(rbtree *T,rbtree_node *x){
     rbtree_node *y = x->right; //

    //1
     x->right = y->left;
     if(y->left != T->nil){
        y->left->parent =  x;
     }
    //2
     y->parent = x->parent;
     if (x->parent == T->nil){ //x是根节点
        T->root = y;
     }
     else if (x == x->parent->left){ //x是左子树
        x->parent->left = y;
     }else{ //x是右子树
        x->parent->right = y;
     }
     //3
     y->left = x;
     x->parent = y;

}

可以直接将x换为y,left换成right。

void _right_rotate(rbtree *T,rbtree_node *y){
     rbtree_node *x = y->left; //

    //1
     y->left = x->right;
     if(x->right != T->nil){
        x->right->parent =  y ;
     }
    //2
     x->parent = y->parent;
     if (y->parent == T->nil){ //x根节点
        T->root = x;
     }
     else if (y == y->parent->right){
        y->parent->right = x;
     }else{
        y->parent->right = x;
     }
     //3
     x->right = y;
     y->parent = x;

}

插入

  1. 新插入的节点是红色的,所以只有当他的父节点是红色的才需要修正。有以下两种:
    (1)父节点是祖父节点的左子树
    (2)父节点是祖父节点的右子树
    上面两个case又分为三个小case:
    (1)叔父节点是红色;
    **将父节点和叔父节点设置为黑色,祖父节点设置为红色,这样叔父节点往下就满足红黑树定义。**接着将祖父节点作为当前节点,继续递归进行修正。
    1.叔节点红色
    (2)叔父节点是黑色,当前节点是父节点的右子树,将父亲节带你作为当前节点,以他为轴,进行左旋
    2.叔父节点是黑色
    (3)叔父节点是黑色,当前节点是父亲接待你的左子树,将父节点设置为黑色,祖父节点设置为红色,并以祖父节点为轴进行右旋
    3.叔父节点为黑色
    对于父亲节点是祖父节点右子树的情况,也可以分为三个case,上面的代码中left换位right就行,right换为left就可以。
    左右子树高度最大相差n/2-1。

void rbtree_fix_up(rbtree *T,rbtree_node *z){

    while(z->parent->color == RED){

        if(z->parent == z->parent->parent->left){
            rbtree_node *y = z->parent->parent->right;
            if(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;
                    _left_rotate(T,z);
                }
                z->parent->color = BLACK;
                z->parent->parent->color = RED;
                _right_rotate(T,z->parent->parent);
            }
        }
    }
}

void rbtree_insert(rbtree *T, rbtree_node *z){
    rbtree_node *x =  T->root;
    rbtree_node *y = T->nil;
     while(x != T->nil){
        y = x;//y一直是x的父节点
        if(z->key < x->key){
            x = x->left;
        }else if(z->key > x->key){
            x=x->right;
        }else{
            return ;
        }
     }

     z-> parent = y;
     if(y == T->nil ){
        T->root = z ;
     }else if(z->key < y->key){
        y->left = z;
     }else{
        y->right = z;
     }

    z->left = T->nil;
    z->right = T->nil;

    z->color = RED;//默认是红色

    rbtree_fix_up(T,z);
    
}

删除节点

可能出现的组合:
红黑树中删除一个节点,遇到的各种情形就是其子节点的状态和颜色的组合,字节点共有三种:无子节点、有一个子节点、有两个子节点,颜色有红色和黑色,总共有六种组合。

组合1:被删节点无子节点,且被删节点为红色。

直接将节点删除,不破坏任何红黑树性质。

组合2:被删节点无子节点,且被删节点为黑色

组合3:被删节点有一个子节点,且被删节点为红色

这项不可能

组合4:被删节点有一个子节点,且被删节点为黑色

这种组合下,被删结点node的另一个子结点value必然为红色,此时直接将node删掉,用value代替node的位置,并将value着黑即可。

组合5或组合6
当删除节点node有两个子节点,我们先找到这个被删除节点的后继节点successor(前驱节点也可以),然后successor替换node的值,不用改变颜色,此时转换为删除node后记节点successor。

应用

案例一、服务器端高并发IO的keep alilve方案,满足一下几个需求
1. 每个IO都是自己的时间戳
2. 每个IO收到自己的beat后,重置自己的定时器
3. 若IO定时没有收到beat,则执行IO的回调函数,并重置定时器
4. 若再次没有收到beat,销毁IO,注销定时器。

参考以下实现3

针对服务器端高并发IO的keepalive方案,可以采用以下思路:

维护一个IO列表,其中每个IO都有一个唯一的ID和一个时间戳,表示上一次收到心跳包的时间。

对于每个IO,设置一个定时器,用于定时发送心跳包和检测是否收到心跳包。定时器的时间间隔可以根据具体情况进行调整。

当服务器接收到客户端的心跳包时,根据心跳包中携带的IO ID,找到对应的IO,并更新其时间戳。

每当一个IO收到心跳包后,重置该IO对应的定时器,以延长其生命周期。

如果一个IO的定时器超时,即表示该IO长时间没有收到心跳包,此时可以执行一个回调函数,例如发送一个警告邮件或关闭该IO连接。

如果一个IO在第二次定时器超时后仍未收到心跳包,则销毁该IO,并注销其定时器。

这样的方案可以满足上述需求,同时还可以灵活地根据实际情况进行调整和优化。需要注意的是,该方案需要考虑并发性和线程安全性,以保证多个线程之间的数据访问不会冲突。
案例二、设计一个线程或者进程的运行体R与运行体调度器S的结构体
1. 运行体R:包含运行状态{新建,准备,挂起{IO等待读,IO等待写,
睡眠,延时}, 退出},运行体回调函数,回调参数
2. 调度器S:包含栈指针,栈大小,当前运行体
3. 调度器S:包含执行集合{就绪,延时,睡眠,等待}

using namespace std;
#include<stack>
#include<iostream>

typedef void(*Callback)(int);

typedef struct _PCB{

    Callback callback; //回调函数
    int paramter;

    int pid; // 进程或线程ID
    int state; // 进程或线程状态,例如:新建,准备,挂起{IO等待读,IO等待写,睡眠,延时}, 退出}
    int priority; // 进程或线程优先级
    int pc; // 程序计数器,指向下一条指令
    void *stack_pointer; // 运行体的栈指针
    // 其他需要的信息,例如进程或线程的上下文信息
} PCB;

//回调函数
void myCallback(int paramter){
    cout<<"myCallback:"<<paramter<<endl;
}

// 运行体调度器结构体
typedef struct _Scheduler{
    PCB *ready_queue; // 就绪队列,用于存储所有处于就绪状态的进程或线程,当前运行体
    stack<int> *process;//进程栈指针
    int num_processes; // 当前活动进程或线程数
    int time_slice; // 时间片,表示每个进程或线程能够执行的时间
    int state;//执行集合{就绪,延时,睡眠,等待
    // 其他需要的信息,例如调度算法类型,进程或线程的状态等
} Scheduler;

  1. https://zhuanlan.zhihu.com/p/273829162 ↩︎

  2. https://cloud.tencent.com/developer/article/1582770 ↩︎

  3. https://blog.csdn.net/yangzhengqui/article/details/79572035 ↩︎

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值