• 随处可见的红黑树


    目录

    红黑树的应用场景

    红黑树的定义

     红黑树结构体

    红黑树的旋转

    左旋

    右旋

    红黑树的插入

    插入结点的颜色

    通过旋转与上色的方式修复第4条性质

    父结点是祖父结点是左子树的情况

     父结点是祖父结点是右子树的情况

    修复的代码

    红黑树删除

    总结


    红黑树的应用场景

    1. c++ stl map,set(红黑树的封装)
    2. 进程调度cfs(用红黑树存储进程的集合,把调度的时间作为key,那么树的左下角时间就是最小的)
    3. 内存管理(每次使用malloc的时候都会分配一块小内存出来,那么这么块就是用红黑树来存,如何表述一段内存块呢,用开始地址+长度来表示,所以key->开始地址,val->大小)
    4. epoll中使用红黑树管理socketfd
    5. nginx中使用红黑树管理定时器,中序遍历第一个就是最小的定时器

    红黑树的定义

    1. 每个结点是红的或者黑的

    2. 根结点是黑的

    3. 每个叶子结点是黑的(因为这条性质,一般用叶子结点在代码中被特殊表示)

    4. 如果一个结点是红的,则它的两个儿子都是黑的(不存在相邻红色)

    5. 从任一节点到叶子节点,所包含的黑色节点数目相同(即黑高度相同)

    6. 最长路径长度不超过最短路径长度的2倍(2n-1,一条黑红黑红…一条全黑)

    2号是红黑树

     红黑树结构体

    1. typedef int KEY_TYPE;
    2. //红黑树定义
    3. typedef struct _rbtree_node {
    4. unsigned char color; //颜色
    5. struct rbtree_node *parent; //颜色
    6. struct rbtree_node *left; //左子树
    7. struct rbtree_node *right; //右子树
    8. KEY_TYPE key;
    9. void *value;
    10. } rbtree_node; //红黑树结点
    11. struct rbtree {
    12. rbtree_node *root; //根结点
    13. rbtree_node *nil; //通用叶子结点
    14. }; //红黑树

    红黑树的旋转

    红黑树性质被破坏的时候,调整。

    动三个方向,改6个指针。
    通过旋转,调整左右高度,使树达到平衡。

    左旋

    降低X结点的高度,提高X结点右结点(即Y)的高度。

    1. x的右子树指向y的左子树
    2. 本来指向x结点的父指针,改成指向y
    3. y的左子树指向x结点
    1. //左旋
    2. //降低X结点的高度,提高X结点右结点(即Y)的高度。
    3. void rbtree_left_rotate(rbtree *T, rbtree_node *x) {
    4. if (x == T->nil) return ;
    5. rbtree_node *y = x->right;
    6. //1
    7. x->right = y->left; //x的右子树指向y的左子树
    8. if (y->left != T->nil) {
    9. y->left->parent = x; //y的左子树的父节点指向x
    10. }
    11. //2
    12. y->parent = x->parent; //y的父结点指向x的父结点
    13. if (x->parent == T->nil) { //如果x是根结点
    14. T->root = y;
    15. } else if (x == x->parent->left) {
    16. x->parent->left = y; //x和父节点的左子树
    17. } else {
    18. x->parent->right = y; //x和父节点的右子树
    19. }
    20. //3
    21. y->left = x; //y的左子树指向x结点
    22. x->parent = y; //x的父节点指向y
    23. }

    右旋

    降低Y结点的高度,提高Y结点左结点(即X)的高度。

    1. y的左子树指向x的右子树
    2. 本来指向y结点的父指针,改成指向x
    3. x的右子树指向y结点

    1. //右旋
    2. //降低Y结点的高度,提高Y结点左结点(即X)的高度。
    3. void _right_rotate(rbtree *T, rbtree_node *y) {
    4. rbtree_node *x = y->left;
    5. //1
    6. y->left = x->right; //y的左子树指向y的右子树
    7. if (x->right != T->nil) {
    8. x->right->parent = y; //x的右子树的父节点指向y
    9. }
    10. //2
    11. x->parent = y->parent; //x的父结点指向y的父结点
    12. if (y->parent == T->nil) { //如果y是根结点
    13. T->root = x;
    14. } else if (y == y->parent->right) {
    15. y->parent->right = x; //y和父节点的右子树
    16. } else {
    17. y->parent->left = x; //y和父节点的左子树
    18. }
    19. //3
    20. x->right = y; //x的左子树指向y结点
    21. y->parent = x; //y的父节点指向y
    22. }

    红黑树的插入

    插入结点的颜色

            在插入结点时,我们始终认为“插入这个结点之前,原来的红黑树是满足红黑树性质的==”,那么插入的位置容易找,就是不断的对比key,最终找到位置,那么新增的结点是什么颜色呢?

    我们通过性质发现:

            如果新结点是黑色,违背了第5条性质

            如果新结点是红色,可能违背第4条性质

    而第四条性质,我们可以通过旋转与上色的方式修复,所以在我们插入结点的时候,我们始终认为新结点是红色

    1. void rbtree_insert(rbtree *T, rbtree_node *z) {
    2. rbtree_node *y = T->nil; //初始化为空
    3. rbtree_node *x = T->root; //根节点
    4. while (x != T->nil) { //x不等于叶子结点
    5. y = x; //y始终指向x前一个位置
    6. if (z->key < x->key) {
    7. x = x->left; //小于插左子树
    8. } else if (z->key > x->key) {
    9. x = x->right; //大于插左子树
    10. } else { //Exist //等于
    11. //如果key相等,看自己的业务情景
    12. //重复插入可以不修改直接退出,可以修改val
    13. }
    14. }
    15. z->parent = y;
    16. if (y == T->nil) { //如果红黑树为空
    17. T->root = z;
    18. } else if (z->key < y->key) {
    19. y->left = z;
    20. } else {
    21. y->right = z;
    22. }
    23. //插入的结点
    24. z->color = RED;
    25. z->left = T->nil; //左右子树为空
    26. z->right = T->nil;
    27. //维护红黑树
    28. rbtree_insert_fixup(T, z); //修复第4条性质
    29. }

    通过旋转与上色的方式修复第4条性质

    性质四:如果一个结点是红的,则它的两个儿子都是黑的(不存在相邻红色)

    我们知道新增结点是红色,如果新结点是父节点也是红色,那么就需要维护红黑树了。

    父结点是祖父结点是左子树的情况

    在下面的图中:z表示新增结点,y表示叔结点

    1. 叔结点是红色的

    将叔结点和父结点变黑,爷结点变红

    2. 叔结点是黑色的,而且当前结点是右孩子

    1. 以父结点为中心左旋
    2. 将父结点变黑色,爷结点变红色
    3. 以爷结点为中心右旋

    3. 叔结点是黑色的,而且当前结点是左孩子

    1. 将父结点变成黑色,爷结点变成红色
    2. 以爷结点为中心右旋

     父结点是祖父结点是子树的情况

    修复的代码

    1. //修复第4条性质
    2. void rbtree_insert_fixup(rbtree *T, rbtree_node *z) {
    3. while (z->parent->color == RED) { //父结点是红色的,需要调整
    4. if (z->parent == z->parent->parent->left) { //如果父结点是爷结点是左子树
    5. rbtree_node *y = z->parent->parent->right; //叔结点
    6. if (y->color == RED) { //叔结点是红色的
    7. //先变色,叔,父变黑
    8. z->parent->color = BLACK;
    9. y->color = BLACK;
    10. //爷结点变红
    11. z->parent->parent->color = RED;
    12. z = z->parent->parent; //递归
    13. }
    14. else {//叔父结点是黑色
    15. if (z == z->parent->right) { //新节点是在右边
    16. z = z->parent;
    17. rbtree_left_rotate(T, z); //以父结点为中心左旋
    18. }
    19. //将父结点变成黑色,爷结点变成红色
    20. z->parent->color = BLACK;
    21. z->parent->parent->color = RED;
    22. //以爷结点为中心右旋
    23. rbtree_right_rotate(T, z->parent->parent);
    24. }
    25. }
    26. }
    27. T->root->color = BLACK; //根节点始终是黑色
    28. }

    红黑树删除

    总结

    红黑树的时间复杂度

    rbTree查询元素:O(log(N))

    rbTree插入元素:插入最多2次旋转,加上查询的时间O(log(N)),插入的复杂度O(log(N))

    rbTree删除元素:删除最多需要3次旋转,加上查询的时间,删除的复杂度O(log(N))

     

     推荐一个不错的学习网站 C/C++后台高级服务器https://ke.qq.com/course/417774?flowToken=1010783 

  • 相关阅读:
    家装、家居两不误,VR全景打造沉浸式家装体验
    Python利器:os与chardet读取多编码文件
    C#:EXCEL列名、列序号之间互相转换
    java虚拟机执行引擎
    XAF中XPO与EFCore的探讨
    足疗APP
    Docker修改数据存储目录
    【JAVA数据结构】二叉树的常用方法(你想要的这里都有)
    CDQ分治学习笔记
    【论文阅读笔记】 Curated Pacific Northwest AI-ready Seismic Dataset
  • 原文地址:https://blog.csdn.net/kakaka666/article/details/126693135