NGINX下的红黑树源码详解(附 流程图和GIF)(2)

简介: 那我们就接着之前的gif继续吧涉及到的 3/4、5情况(精简版)情况3:变化前[当前结点为4节点]:当前节点的父节点是红色且祖父节点的另一个子节点(叔叔节点)是红色。

NGINX下的红黑树源码详解(附 流程图和GIF)(1):https://developer.aliyun.com/article/1415930

那我们就接着之前的gif继续吧

涉及到的 3/4、5情况(精简版)

情况3:变化前[当前结点为4节点]:

当前节点的父节点是红色且祖父节点的另一个子节点(叔叔节点)是红色。

对策:将当前节点的父节点和叔叔节点涂黑,祖父节点涂红,把当前节点指向祖父节点,从新的当前节点重新开始算法。


情况4:[当前节点为7节点]:

当前节点的父节点是红色,叔叔节点是黑色,当前节点是其父节点的右子


对策:当前节点的父节点做为新的当前节点,以新当前节点为支点左旋。


情况5:[当前节点为2节点]

当前节点的父节点是红色,叔叔节点是黑色,当前节点是其父节点的左子


对策:父节点变为黑色,祖父节点变为红色,把祖父节点右旋

image.gif


下面是屏幕截图:(实在觉得网页不够看,博主鼓励多开几个!!!)

20191103160323972.png

20191103160328683.png

20191103160341448.png

20191103160354224.png20191103160346787.png

20191103160354224.png

20191103160359456.png


看到没有,以上就只是对着原理进行分析都能这么长(相信大家应该知道了吧),但是有时候知道原理并不代表你能看懂代码(博主亲身体验!!)所以博主就对着代码来给大家分析一下红黑树染完色之后的左右旋吧(只讲左右旋就已经很复杂了。希望大家对红黑树的理解更加深刻!!!!!)


下面是情况四的左旋:


我就把有用的代码粘过来了:

//之前博客的情况4
      } else {    //如果父结点的右兄弟是黑的
        if (node == node->parent->right) {  //如果新结点是右子结点
          node = node->parent;    //父结点成为新node
          ngx_rbtree_left_rotate(root, sentinel, node); //node左旋 --->跳转到子函数
        }
//左旋
static ngx_inline void
ngx_rbtree_left_rotate(ngx_rbtree_node_t **root, ngx_rbtree_node_t *sentinel,
    ngx_rbtree_node_t *node)  //红黑树的左旋
{
    ngx_rbtree_node_t  *temp; //定义一个临时变量
    temp = node->right;     //获取当前右节点 此时temp就是当前节点的右节点了
    node->right = temp->left; ///node的右节点设置为他原来右节点的左节点
    if (temp->left != sentinel) { //如果右子结点的左结点不为哨兵
        temp->left->parent = node;  //右子结点的左子结点挂在左旋结点上
    }
    temp->parent = node->parent;  //右节点将会变成原来node的父节点。
    if (node == *root) {      //是不是根节点的判断
        *root = temp;
    } else if (node == node->parent->left) {  //然后把右节点的信息和原来node的parent进行维护
        node->parent->left = temp;
    } else {
        node->parent->right = temp;
    }
    temp->left = node;    //现在node变回他原来右节点的子节点了
    node->parent = temp;  //所以他的parent变成temp
}

染色的博主就不管了,博主这个GIF只是针对于旋转代码是如何弄的。

虽然说上面的连线是有点乱的,但是精髓博主全部都讲到了,好好理解一定能掌握的!!!!(我只能穿模糊的了,大家可以自己找到原图跟着博主的思路)

20191103171356404.gif

画图太小,导致有一条语句丢失,博主画完图又仔细看了一下逻辑,不然大家可能会接受到不完成的旋转

20191103171629428.gif


最后的图片,大家可以对照:

20191103170821377.png

20191103170829539.png经过情况四之后的情况五的右旋:

  ngx_rbt_black(node->parent);    //node的父结点染黑
                ngx_rbt_red(node->parent->parent);    //node的父结点的父结点染红
                ngx_rbtree_right_rotate(root, sentinel, node->parent->parent);//node的父结点的父结点右旋 ---->跳转到子函数
static ngx_inline void
ngx_rbtree_right_rotate(ngx_rbtree_node_t **root, ngx_rbtree_node_t *sentinel,
    ngx_rbtree_node_t *node)  //红黑树的右旋
{
    ngx_rbtree_node_t  *temp;
    temp = node->left;
    node->left = temp->right; //左子结点指向原左子结点的右结点
    if (temp->right != sentinel) {  //如果左子结点的右结点不为哨兵
        temp->right->parent = node; //左子结点的右子结点挂在右旋结点上
    }
    temp->parent = node->parent;  //左子结点挂在右旋结点的父结点上
    if (node == *root) {  //如果右旋结点为根节点
        *root = temp;   //根节点赋为左子结点
    } else if (node == node->parent->right) { //如果右旋结点为右子结点
        node->parent->right = temp;       //左子结点挂父结点右边
    } else {    //否则左子结点挂父结点左边
        node->parent->left = temp;
    }
    temp->right = node;   //现在node变回他原来左节点的子节点了
    node->parent = temp;  //所以他的parent变成temp
}

20191103173306502.gif

20191103174344488.png

再看看我们的插入代码是不是会有感觉了呢??

void
ngx_rbtree_insert(ngx_rbtree_t *tree, ngx_rbtree_node_t *node)
{
    ngx_rbtree_node_t  **root, *temp, *sentinel;  //这三个都有独立的结构
    /* a binary tree insert */
    root = &tree->root;       //树根指针赋给了root
    sentinel = tree->sentinel;    //哨兵指针赋给了哨兵指针
    //如果树是空树的话,那么插入的结点就会变成根节点,伴随着的左右子节点就会变成哨兵
    if (*root == sentinel) {    //特判,如果根是哨兵,即树是空的
        node->parent = NULL;    //新插入的结点变成了根
        node->left = sentinel;    //新结点的左子结点是哨兵
        node->right = sentinel;   //新结点的右子结点也是哨兵
        ngx_rbt_black(node);    //新根染黑
        *root = node;       //确认新结点为新根
        return;     //插入结束
    }
  //树初始化时给了insert指针一个函数地址
    //查看前面的宏ngx_rbtree_init(tree, s, i)
    //发现只是把指定结点染黑,同时赋为根和哨兵,给insert指针指定一个函数
    //ngx_rbtree.c中有两个参数表符合的可选函数:插入值、插入计时器值
    //稍后来看两种插入分别如何实现又有什么区别
      // insert 就对应 .h的那一个函数指针(61,54 行就能看懂了)
      // 函数指针里面的参数有 根节点、插入结点、哨兵结点的指针
    tree->insert(*root, node, sentinel);  //插入操作(通过这个函数指针的调用就能找到我们的插入点了)
    /* 若红黑树不为空,则按照二叉查找树的插入操作进行
     * 该操作由函数指针提供(跳转到插入函数)
     */
    /* re-balance tree */
    //如果新结点不是根结点而且其父结点是红的,循环
    while (node != *root && ngx_rbt_is_red(node->parent)) { 
//最坏的情况,就是 情况:3,4,5全部走一遍
  //  之前博客的情况3
        if (node->parent == node->parent->parent->left) { //如果父结点是左子结点,temp 获得父结点的右兄弟
            temp = node->parent->parent->right;
            if (ngx_rbt_is_red(temp)) {     //如果父结点的右兄弟是红的
                ngx_rbt_black(node->parent);  //父结点染黑
                ngx_rbt_black(temp);      //父结点的右兄弟染黑
                ngx_rbt_red(node->parent->parent);    //父结点的父结点染红
                node = node->parent->parent;      //父结点的父结点成为当前结点,继续去维护红黑树性质
  //之前博客的情况4
            } else {    //如果父结点的右兄弟是黑的
                if (node == node->parent->right) {  //如果新结点是右子结点
                    node = node->parent;    //父结点成为新node
                    ngx_rbtree_left_rotate(root, sentinel, node); //node左旋 --->跳转到子函数
                } 
        //之前博客的情况5
                ngx_rbt_black(node->parent);    //node的父结点染黑
                ngx_rbt_red(node->parent->parent);    //node的父结点的父结点染红
                ngx_rbtree_right_rotate(root, sentinel, node->parent->parent);//node的父结点的父结点右旋 ---->跳转到子函数
            }
        } 
  //与上面的情况对称  
      else {  //如果父结点是右子结点,获得父结点的左兄弟
            temp = node->parent->parent->left;
            if (ngx_rbt_is_red(temp)) {//如果父结点的左兄弟是红的
                ngx_rbt_black(node->parent);  //父结点染黑
                ngx_rbt_black(temp);      //父结点的左兄弟染黑
                ngx_rbt_red(node->parent->parent);  //父结点的父结点染红
                node = node->parent->parent;
            } else {      //如果父结点的左兄弟是黑的
                if (node == node->parent->left) { //如果新结点是左子结点
                    node = node->parent;    //父结点成为当前结点
                    ngx_rbtree_right_rotate(root, sentinel, node);  //当前结点右旋
                }
                ngx_rbt_black(node->parent);    //当前结 点染黑
                ngx_rbt_red(node->parent->parent);  //当前结点父结点的父结点染红
                ngx_rbtree_left_rotate(root, sentinel, node->parent->parent); //当前结点的父结点的父结点左旋
            }
        }
    }
    ngx_rbt_black(*root); //根结点染黑,因为根节点必须为黑色
}
//然后是对应ngx_rbtree_insert_pt指针的基础的结点插入函数:
    //上文提到nginx提供一种机制自定义插入函数解决key值冲突,不过nginx提供几个默认的供我们使用:
//root是容器指针
//node是待插入节点指针
//sentinel是哨兵节点
//向红黑树插入节点,每个节点key都是唯一的
void
ngx_rbtree_insert_value(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node,
    ngx_rbtree_node_t *sentinel)
{//根节点temp、插入结点node、哨兵结点的指针sentinel
    ngx_rbtree_node_t  **p;
    for ( ;; ) {//无条件循环或者说死循环,等同于while(1)但节省了一个字符,为了找到插入点
        p = (node->key < temp->key) ? &temp->left : &temp->right; //三目运算符,为了找到合适的插入点
        if (*p == sentinel) { //在二叉树中查找新结点合适的叶结点位置
            break;  //跳出循环,此时就可以进行插入了
        }
        temp = *p;
    } //当跳出这个循环的时候,那么我们就找到了合适的插入位置了
    //令新结点占据合适的哨兵位置成为新的叶结点,染红,产生新哨兵
    *p = node;
  //插入操作
    node->parent = temp;  
    node->left = sentinel;
    node->right = sentinel;
    ngx_rbt_red(node);
}
static ngx_inline void
ngx_rbtree_left_rotate(ngx_rbtree_node_t **root, ngx_rbtree_node_t *sentinel,
    ngx_rbtree_node_t *node)  //红黑树的左旋
{
    ngx_rbtree_node_t  *temp; //定义一个临时变量
    temp = node->right;     //获取当前右节点 此时temp就是当前节点的右节点了
    node->right = temp->left; ///node的右节点设置为他原来右节点的左节点
    if (temp->left != sentinel) { //如果右子结点的左结点不为哨兵
        temp->left->parent = node;  //右子结点的左子结点挂在左旋结点上
    }
    temp->parent = node->parent;  //右节点将会变成原来node的父节点。
    if (node == *root) {      //是不是根节点的判断
        *root = temp;
    } else if (node == node->parent->left) {  //然后把右节点的信息和原来node的parent进行维护
        node->parent->left = temp;
    } else {
        node->parent->right = temp;
    }
    temp->left = node;    //现在node变回他原来右节点的子节点了
    node->parent = temp;  //所以他的parent变成temp
}
static ngx_inline void
ngx_rbtree_right_rotate(ngx_rbtree_node_t **root, ngx_rbtree_node_t *sentinel,
    ngx_rbtree_node_t *node)  //红黑树的右旋
{
    ngx_rbtree_node_t  *temp;
    temp = node->left;
    node->left = temp->right; //左子结点指向原左子结点的右结点
    if (temp->right != sentinel) {  //如果左子结点的右结点不为哨兵
        temp->right->parent = node; //左子结点的右子结点挂在右旋结点上
    }
    temp->parent = node->parent;  //左子结点挂在右旋结点的父结点上
    if (node == *root) {  //如果右旋结点为根节点
        *root = temp;   //根节点赋为左子结点
    } else if (node == node->parent->right) { //如果右旋结点为右子结点
        node->parent->right = temp;       //左子结点挂父结点右边
    } else {    //否则左子结点挂父结点左边
        node->parent->left = temp;
    }
    temp->right = node;   //现在node变回他原来左节点的子节点了
    node->parent = temp;  //所以他的parent变成temp
}


另一种情况就是我们的镜像了,就不再演示

流程图(画的比教粗糙):

20191103184132700.png

以上就是我们 NGINX有关红黑树插入的全部内容了。上面GIF有点模糊所以想要看完整的视频可以点击我的百度网盘进行观看

本博客篇幅已经够长了如果博主再讲解红黑树的删除只怕打架承受不住,所以想要了解删除的,期待博主的下一篇博客吧。


目录
相关文章
|
7月前
|
应用服务中间件 Linux 网络安全
CentOS 7.4源码编译nginx1.12 并且隐藏nginx的版本
CentOS 7.4源码编译nginx1.12 并且隐藏nginx的版本
129 0
|
3月前
|
负载均衡 网络协议 应用服务中间件
web群集--rocky9.2源码部署nginx1.24的详细过程
Nginx 是一款由 Igor Sysoev 开发的开源高性能 HTTP 服务器和反向代理服务器,自 2004 年发布以来,以其高效、稳定和灵活的特点迅速成为许多网站和应用的首选。本文详细介绍了 Nginx 的核心概念、工作原理及常见使用场景,涵盖高并发处理、反向代理、负载均衡、低内存占用等特点,并提供了安装配置教程,适合开发者参考学习。
|
4月前
|
应用服务中间件 Linux nginx
在CentOS上使用源码包安装Nginx、以及手动启动Nginx的步骤过程
这篇文章介绍了在CentOS系统上使用Nginx源码包进行安装和配置的详细步骤,包括源码包的获取、解压、配置、编译、安装、启动验证以及注意事项。
439 0
在CentOS上使用源码包安装Nginx、以及手动启动Nginx的步骤过程
|
7月前
|
消息中间件 Java 关系型数据库
JAVA云HIS医院管理系统源码、基于Angular+Nginx+ Java+Spring,SpringBoot+ MySQL + MyCat
JAVA云HIS医院管理系统 常规模版包括门诊管理、住院管理、药房管理、药库管理、院长查询、电子处方、物资管理、媒体管理等,为医院管理提供更有力的保障。 HIS系统以财务信息、病人信息和物资信息为主线,通过对信息的收集、存储、传递、统计、分析、综合查询、报表输出和信息共享,及时为医院领导及各部门管理人员提供全面、准确的各种数据。
144 1
|
7月前
|
前端开发 应用服务中间件 网络安全
nginx配置SSL证书配置https访问网站 超详细(附加配置源码+图文配置教程)
nginx配置SSL证书配置https访问网站 超详细(附加配置源码+图文配置教程)
703 0
|
7月前
|
应用服务中间件 nginx C++
nginx内存池源码剖析
小块内存使用尾插法,大块内存使用头插法,大块内存申请挂载时如果前面三个节点都有对象,则不再向后遍历,直接创建一个节点插在头部如果一个小块内存超过四次都没有成功分配出内存,则认为他已经没有足够的内存了,则会抛弃(之后不在考虑在这个节点上分配内存)小块内存节点的内存不回收,但是大内存块的节点可以回收,提供回收方法内存对齐,多处内存对齐减少内存跨 cache 的数量。
77 0
nginx内存池源码剖析
|
7月前
|
应用服务中间件 nginx
Nginx源码阅读:共享内存ngx_shm_t和它的组织方式ngx_shm_zone_t、ngx_list_t
Nginx源码阅读:共享内存ngx_shm_t和它的组织方式ngx_shm_zone_t、ngx_list_t
83 0
|
7月前
|
应用服务中间件 nginx
Nginx源码阅读:ngx_list_t 链表
Nginx源码阅读:ngx_list_t 链表
107 0
|
7月前
|
存储 网络协议 应用服务中间件
Nginx源码阅读:ngx_palloc 内存池
Nginx源码阅读:ngx_palloc 内存池
132 0
|
7月前
|
存储 应用服务中间件 nginx
Nginx模块开发:模块结构的源码阅读以及过滤器(Filter)模块的实现
Nginx模块开发:模块结构的源码阅读以及过滤器(Filter)模块的实现
213 0