引言:红黑树(英语:Red–black tree)是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组。它是在1972年由鲁道夫·贝尔发明的,他称之为"对称二叉B树",它现代的名字是在Leo J. Guibas和Robert Sedgewick于1978年写的一篇论文中获得的。它是复杂的,但它的操作有着良好的最坏情况运行时间,并且在实践中是高效的:它可以在 O(log n) 时间内做查找,插入和删除,这里的n是树中元素的数目。——摘自维基百科

红黑树的性质:
红黑树是每个节点都带有颜色属性的二叉查找树,颜色为红色或黑色。在二叉查找树强制一般要求以外,对于任何有效的红黑树有如下的额外要求:

1.节点是红色或黑色。
2.根是黑色。
3.所有叶子都是黑色(叶子是NIL节点)。
4.每个红色节点必须有两个黑色的子节点。(从每个叶子到根的所有路径上不能有两个连续的红色节点。)
5.从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。

这个网站可以看各种数据结构的动态演变
https://www.cs.usfca.edu/~galles/visualization/Algorithms.html

操作:
因为每一个红黑树也是一个特化的二叉查找树,因此红黑树上的只读操作与普通二叉查找树上的只读操作相同。然而,在红黑树上进行插入操作和删除操作会导致不再匹配红黑树的性质。恢复红黑树的性质需要少量 O(log n)的颜色变更(实际是非常快速的)和不超过三次树旋转(对于插入操作是两次)。虽然插入和删除很复杂,但操作时间仍可以保持为 O(log n)次。

红黑树的应用
它的统计性能要好于平衡二叉树(AVL树),因此,红黑树在很多地方都有应用。比如
①JDK1.8中的TreeMap 或
②C++的STL中的set和map函数或
③Linux内核中的rbtree.h和rbtree.c
都有红黑树的应用,这些集合均提供了很好的性能。

什么时候用AVL树?什么时候用红黑树?
首先AVL树和红黑树都是基于BST树(二叉搜索树),对于只读操作均和BST树相同,但BST树连续插入顺序递增或递减结点时会导致BST树退化成链表,性能大幅度降低。BST顺序插入如图:

因此,AVL树和红黑树均在BST树的基础上进行了进一步的优化。以增加旋转操作(增加算法复杂度)来提升对于最坏情况下的性能。提升复杂度往往从两方面入手:
①提升数据结构的复杂度
②提升算法的复杂度
其中,红黑树为了降低原有算法复杂度,就以提升数据结构复杂度来间接减轻其算法复杂度。即在其结点中增加颜色属性,颜色只能有红色和黑色。在红黑树中只要任意结点左右孩子的黑色高度平衡(两边任意路径黑色结点数量相同)即可且对于有些情况可以通过变色来代替旋转从而减少一定的空间开销。

①AVL树采用平衡因子的绝对值<2来保证平衡,这种平衡是高度平衡的。要保证这种平衡需要更复杂的操作来维持。
②红黑树采用同一结点到叶结点的任意路径黑色结点数量相同来保证平衡,这种平衡只能保证从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。保证这种平衡只需要较少的操作来维持。

下图是从根到叶子的最长的可能路径=最短的可能路径的两倍长的情况:

综上所述:删除结点最坏情况下,AVL树需要旋转的量级是O(log n),而红黑树最多只需3次旋转,只需要O(1)的复杂度。

下图是知乎大神提供的性能测试:

由此可见:
①红黑树由于插入删除时较少的旋转操作,对于需要频繁进行插入删除的场景性能比AVL树好
②反之,对于不需要频繁插入删除的场景。由于AVL树的高度平衡,使得同等结点数量下,AVL树的高度比红黑树更低,使得AVL树的查找性能比红黑树好。

JDK1.8里的TreeMapEntry对红黑树数据结构的定义
/**
* Node in the Tree. Doubles as a means to pass key-value pairs back to
* user (see Map.Entry).
*/

static final class TreeMapEntry<K,V> implements Map.Entry<K,V> {K key;V value;TreeMapEntry<K,V> left = null;//左子树TreeMapEntry<K,V> right = null;//右子树TreeMapEntry<K,V> parent;//父节点boolean color = BLACK;//默认为黑色/*** Make a new cell with given key, value, and parent, and with* {@code null} child links, and BLACK color.*/TreeMapEntry(K key, V value, TreeMapEntry<K,V> parent) {this.key = key;this.value = value;this.parent = parent;}/*** Returns the key.** @return the key*/public K getKey() {return key;}/*** Returns the value associated with the key.** @return the value associated with the key*/public V getValue() {return value;}/*...各种方法*/

Linux内核文件rbtree.h中对红黑树的数据结构的定义
struct rb_node
{/long是四个字节,占用共4x8=32bit,其中有两位存储父结点指针+自己的颜色值/
unsigned long rb_parent_color; /注意:此处保存的是父指针+自己颜色/
#define RB_RED 0
#define RB_BLACK 1
struct rb_node rb_right;
struct rb_node rb_left;
} /
attribute((aligned(sizeof(long))))
/;
/* The alignment might seem pointless, but allegedly CRIS needs it */

struct rb_root
{
struct rb_node *rb_node;
};

以及rbtree.h中在插入新结点时,初始化红黑树结点操作

static inline void rb_init_node(struct rb_node *rb)
{
rb->rb_parent_color = 0;
rb->rb_right = NULL;
rb->rb_left = NULL;
RB_CLEAR_NODE(rb);
}

在此需要先了解左旋和右旋的原理
结点E左旋操作:

(动图来源于网络)
代码如下:
/*

  • 左旋操作Linux c代码(来源于Linux内核rbtree.c)
    */
    static void __rb_rotate_left(struct rb_node *node, struct rb_root *root)
    {
    struct rb_node *right = node->rb_right;
    struct rb_node *parent = rb_parent(node);

    if ((node->rb_right = right->rb_left)) //node的右指针指向node右孩子的左孩子
    rb_set_parent(right->rb_left, node);//node右孩子的左孩子的父亲指定为node
    right->rb_left = node;

    rb_set_parent(right, parent); //node右孩子祖父指定为node原父亲

    if (parent) //node原父亲存在,即原node不是根结点
    {
    if (node == parent->rb_left)
    parent->rb_left = right;
    else
    parent->rb_right = right;
    }
    else //原node是根结点
    root->rb_node = right;
    rb_set_parent(node, right); //node现任父亲指定为node原右孩子
    }

结点S右旋操作:

(动图来源于网络,代码与左旋对调)

红黑树插入操作逻辑
https://naotu.baidu.com/file/c0fa8088fa5643598484bae7e81fbb49?token=cc0609045c2ebee9

红黑树删除操作逻辑
https://naotu.baidu.com/file/70158a0693b8421b3bd0fe56cde6e361?token=6e76ac0c14851a5b

Linux内核源码注释解析
rbtree.c
/*
Red Black Trees
© 1999 Andrea Arcangeli andrea@suse.de
© 2002 David Woodhouse dwmw2@infradead.org

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

linux/lib/rbtree.c
*/

#include “rbtree.h”
/*

  • 左旋操作
    */
    static void __rb_rotate_left(struct rb_node *node, struct rb_root *root)
    {
    struct rb_node *right = node->rb_right;
    struct rb_node *parent = rb_parent(node);

    if ((node->rb_right = right->rb_left)) //node的右指针指向node右孩子的左孩子
    rb_set_parent(right->rb_left, node);//node右孩子的左孩子的父亲指定为node
    right->rb_left = node;

    rb_set_parent(right, parent); //node右孩子祖父指定为node原父亲

    if (parent) //node原父亲存在,即原node不是根结点
    {
    if (node == parent->rb_left)
    parent->rb_left = right;
    else
    parent->rb_right = right;
    }
    else //原node是根结点
    root->rb_node = right;
    rb_set_parent(node, right); //node现任父亲指定为node原右孩子
    }
    /*

  • 右旋操作
    */
    static void __rb_rotate_right(struct rb_node *node, struct rb_root *root)
    {
    struct rb_node *left = node->rb_left;
    struct rb_node *parent = rb_parent(node);

    if ((node->rb_left = left->rb_right))
    rb_set_parent(left->rb_right, node);
    left->rb_right = node;

    rb_set_parent(left, parent);

    if (parent)
    {
    if (node == parent->rb_right)
    parent->rb_right = left;
    else
    parent->rb_left = left;
    }
    else
    root->rb_node = left;
    rb_set_parent(node, left);
    }
    /*

  • 插入结点操作
    */
    void rb_insert_color(struct rb_node *node, struct rb_root *root)
    {
    struct rb_node *parent, *gparent;
    //情况1,2:node不是根结点,即有父结点P且P是红色的
    while ((parent = rb_parent(node)) && rb_is_red(parent))
    {
    gparent = rb_parent(parent);
    //P是祖父G的左孩子
    if (parent == gparent->rb_left)
    {
    {
    register struct rb_node *uncle = gparent->rb_right;
    if (uncle && rb_is_red(uncle)) //情况3:node的叔父结点是红色的
    {
    rb_set_black(uncle);
    rb_set_black(parent);
    rb_set_red(gparent);
    node = gparent;
    continue;
    }
    }
    //情况4:node和父结点P是LR型(变成LL型)
    if (parent->rb_right == node)
    {
    register struct rb_node *tmp;
    __rb_rotate_left(parent, root);
    tmp = parent;
    parent = node;
    node = tmp;
    }
    //情况5:node和父结点P是LL型
    rb_set_black(parent);
    rb_set_red(gparent);
    __rb_rotate_right(gparent, root);
    } else { //P是祖父G的右孩子,与上述情况对调
    {
    register struct rb_node *uncle = gparent->rb_left;
    if (uncle && rb_is_red(uncle))
    {
    rb_set_black(uncle);
    rb_set_black(parent);
    rb_set_red(gparent);
    node = gparent;
    continue;
    }
    }

         if (parent->rb_left == node){register struct rb_node *tmp;__rb_rotate_right(parent, root);tmp = parent;parent = node;node = tmp;}rb_set_black(parent);rb_set_red(gparent);__rb_rotate_left(gparent, root);}
    

    }
    //若node是根结点||node的父结点P是黑色的,则把根结点->黑色
    rb_set_black(root->rb_node);
    }
    /*

  • 删除结点操作(Node和Child均是黑色的情况)
    */
    static void __rb_erase_color(struct rb_node *node, struct rb_node *parent,
    struct rb_root *root)
    {
    struct rb_node other;
    //以下循环体条件node不是根结点&&(node非空||node是黑色的)
    while ((!node || rb_is_black(node)) && node != root->rb_node)
    {
    if (parent->rb_left == node) //若node是父结点的左孩子
    {
    other = parent->rb_right;
    if (rb_is_red(other)) //N的兄弟结点S是红色的
    { //S由红色->黑色;P由黑色->红色;P左旋
    rb_set_black(other);
    rb_set_red(parent);
    __rb_rotate_left(parent, root);
    other = parent->rb_right;
    } //SL黑色&&SR黑色
    if ((!other->rb_left || rb_is_black(other->rb_left)) &&
    (!other->rb_right || rb_is_black(other->rb_right)))
    {
    rb_set_red(other);
    node = parent; //将P作为node带入以下检查程序
    parent = rb_parent(node);
    }
    else
    { //SL是红色(推断),SR是黑色
    if (!other->rb_right || rb_is_black(other->rb_right))
    { //SL由红色->黑色;S由黑色->红色;右旋S;P的新右孩子是SL
    rb_set_black(other->rb_left);
    rb_set_red(other);
    __rb_rotate_right(other, root);
    other = parent->rb_right;
    } //S由黑色->P的颜色;P->黑色;SR由红色->黑色;左旋P
    rb_set_color(other, rb_color(parent));
    rb_set_black(parent);
    rb_set_black(other->rb_right);
    __rb_rotate_left(parent, root);
    node = root->rb_node;
    break;
    }
    }
    else //若node是父结点的右孩子,和上述情况对调
    {
    other = parent->rb_left;
    if (rb_is_red(other))
    {
    rb_set_black(other);
    rb_set_red(parent);
    __rb_rotate_right(parent, root);
    other = parent->rb_left;
    }
    if ((!other->rb_left || rb_is_black(other->rb_left)) &&
    (!other->rb_right || rb_is_black(other->rb_right)))
    {
    rb_set_red(other);
    node = parent;
    parent = rb_parent(node);
    }
    else
    {
    if (!other->rb_left || rb_is_black(other->rb_left))
    {
    rb_set_black(other->rb_right);
    rb_set_red(other);
    __rb_rotate_left(other, root);
    other = parent->rb_left;
    }
    rb_set_color(other, rb_color(parent));
    rb_set_black(parent);
    rb_set_black(other->rb_left);
    __rb_rotate_right(parent, root);
    node = root->rb_node;
    break;
    }
    }
    }
    if (node)
    rb_set_black(node);
    }
    /

  • 删除红黑树结点,处理除Node和Child均是黑色以外的情况,最后单独将这种情况用__rb_erase_color函数处理
    */
    void rb_erase(struct rb_node *node, struct rb_root *root)
    {
    struct rb_node *child, *parent;
    int color;

    if (!node->rb_left) //1.删除节点node左孩子为空
    child = node->rb_right; //child为node右孩子
    else if (!node->rb_right)//2.删除节点node右孩子为空
    child = node->rb_left; //child为node左孩子
    else //3.删除节点node子结点均非空,则需要找到其右子树的最小的结点然后跟换两个结点的值
    {
    struct rb_node *old = node, *left;

     node = node->rb_right; //令node为原node的右孩子while ((left = node->rb_left) != NULL) //不断循环找到其最左的孩子(最小值结点)node = left;if (rb_parent(old)) {  //若原node(old)有父结点if (rb_parent(old)->rb_left == old) //若old是父结点的左孩子,则现在左孩子是新noderb_parent(old)->rb_left = node;elserb_parent(old)->rb_right = node;//若old是父结点的右孩子,则现在右孩子是新node} elseroot->rb_node = node; //若原node(old)是根结点,则现在的根是新nodechild = node->rb_right; //新node的右孩子的是childparent = rb_parent(node);//新node的父亲的是parentcolor = rb_color(node);//新node的颜色是color//以下将old替换为新node结点,同时将新node的非空子结点child成为P的左孩子if (parent == old) {parent = node;} else {if (child)rb_set_parent(child, parent);parent->rb_left = child;  //P的左孩子由node变为childnode->rb_right = old->rb_right;rb_set_parent(old->rb_right, node);}node->rb_parent_color = old->rb_parent_color;  //Linux内核中红黑树数据结构存储的是其父结点的颜色node->rb_left = old->rb_left;rb_set_parent(old->rb_left, node);goto color;  //goto到"color:"一行
    

    }
    //以下步骤是对换情况1和2的node和child两结点,使child为node的新父亲
    parent = rb_parent(node);
    color = rb_color(node);

    if (child)
    rb_set_parent(child, parent);
    if (parent)
    {
    if (parent->rb_left == node)
    parent->rb_left = child;
    else
    parent->rb_right = child;
    }
    else
    root->rb_node = child;
    //以上步骤是对换情况1和2的node和child两结点,使child为node的新父亲
    color:
    if (color == RB_BLACK) //若和child颠倒位置后的node结点是黑色
    __rb_erase_color(child, parent, root); //将child带入检查程序
    }
    /*

  • 替换结点函数(将结点victim替换为结点new)
    */
    void rb_replace_node(struct rb_node *victim, struct rb_node *new,
    struct rb_root *root)
    {
    struct rb_node *parent = rb_parent(victim);

    /* Set the surrounding nodes to point to the replacement */
    if (parent) {
    if (victim == parent->rb_left)
    parent->rb_left = new;
    else
    parent->rb_right = new;
    } else {
    root->rb_node = new;
    }
    if (victim->rb_left)
    rb_set_parent(victim->rb_left, new);
    if (victim->rb_right)
    rb_set_parent(victim->rb_right, new);

    /* Copy the pointers/colour from the victim to the replacement */
    new = victim;
    }
    /
    …后续扩展函数等
    /
    ————————————————
    版权声明:本文为CSDN博主「SL_World」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/SL_World/article/details/84584503

红黑树原理浅谈(附Linux内核源码注释)相关推荐

  1. 详解5种红黑树的场景,从Linux内核谈到Nginx源码,听完醍醐灌顶丨Linux服务器开发丨Linux后端开发

    5种红黑树的场景,从Linux内核谈到Nginx源码,听完醍醐灌顶 1. 进程调度CFS的红黑树场景 2. 虚拟内存管理的红黑树场景 3. 共享内存slab的红黑树场景 视频讲解如下,点击观看: [干 ...

  2. 腾讯首发Linux内核源码《嵌入式开发进阶笔记》差距差的不止一点点哦

    一,前言 Linux内核是一个操作系统(OS)内核,本质上定义为类Unix.它用于不同的操作系统,主要是以不同的Linux发行版的形式.Linux内核是第一个真正完整且突出的免费和开源软件示例.Lin ...

  3. Linux内核源码分析《进程管理》

    Linux内核源码分析<进程管理> 前言 1. Linux 内核源码分析架构 2. 进程原理分析 2.1 进程基础知识 2.2 Linux进程四要素 2.3 进程描述符 task_stru ...

  4. 玩转腾讯首发Linux内核源码《嵌入式开发笔记》

    一.前言 Linux内核是一种开源电脑操作系统内核,它是一个用C语言写成,符合POSIX标准的类Unix操作系统. Linux内核最早是由芬兰Linus Torvalds为尝试在英特尔x86架构上提供 ...

  5. Linux内核源码分析之内存管理

    本文站的角度更底层,基本都是从Linux内核出发,会更深入.所以当你都读完,然后再次审视这些功能的实现和设计时,我相信你会有种豁然开朗的感觉. 1.页 内核把物理页作为内存管理的基本单元. 尽管处理器 ...

  6. 深入分析Linux内核源码oss.org.cn/kernel-book/

    本html页面地址:http://oss.org.cn/kernel-book/ 深入分析Linux内核源码 前言         第一章 走进linux 1.1 GNU与Linux的成长 1.2 L ...

  7. Linux内核源码分析方法

    说明:这是一个刚接触内核三个月的同学,通过对一个内核特性的分析,总结出一套分析内核的方法. 一.内核源码之我见 Linux内核代码的庞大令不少人"望而生畏",也正因为如此,使得人们 ...

  8. linux的进程/线程/协程系列3:查看linux内核源码——vim+ctags/find+grep

    linux的进程/线程/协程系列3:查看linux内核源码--vim+ctags/find+grep 前言 摘要: 1. 下载linux内核源码 2. 打标签方法:vim+ctags 2.1 安装vi ...

  9. 如果你打算看完Linux内核源码,可能穷尽一生都做不出一个系统

    关注.星标公众号,直达精彩内容 来源:技术让梦想更伟大 整理:李肖遥 代码并不是写给人看的,而是交给机器运行的. 所以我们去理解别人的代码时,并不能像看小说一样去通篇的阅读代码,而应该是像研究化石一样 ...

最新文章

  1. 【控制】《多智能体系统一致性与复杂网络同步控制》郭凌老师-第1章-绪论
  2. button按钮onclick触发不了_单按钮启停:测试模拟脉冲发生器的动作
  3. LeetCode 1658. 将 x 减到 0 的最小操作数(哈希)
  4. 这个省3月23日开始错时错峰开学,在校不要求戴口罩!
  5. 美赛建模需要matlab吗,美赛(matlab自学)之微分方程建模
  6. Python学习笔记之列表(五)
  7. Android游戏开发基本知识
  8. matlab中rgb转hsv,matlab实现RGB与HSV(HSB)、HSL和HSI的色彩空间互转
  9. 理解UIScrollView
  10. Redis多线程执行 -- 过程分析
  11. 22.docker wait
  12. 载入java VM时出错216_Android6.0中oat文件的加载过程
  13. 如何下载spring源码?
  14. Retrofit使用教程(一)- Retrofit入门详解
  15. 基于Html+Css+javascript的体育网站
  16. 【Python表白小程序】表白神器——赶紧收藏起来~
  17. 花椒六间房“花房之夜”落幕 全新升级不止心动
  18. matlab支持 编程语言,用于数学的10个优秀编程语言
  19. 『迷你教程』识别人类活动的一维卷积神经网络模型,附完整代码
  20. 利用pgzero做一个接球的小游戏

热门文章

  1. mysql排序convert_MYSQLVARCHAR排序CAST,CONVERT函数类型转换
  2. iTunes备份在哪里,iTunes备份文件路径
  3. 百度研究院research实习生面经-2020年10月
  4. 我的Android成长之路——支付宝的沙箱测试
  5. 爬虫之非结构化数据爬取:字符串find,split应用
  6. @ConditionalOnBean、@ConditionalOnProperty、@ConditionalOnClass、@Conditional和Condition接口的使用
  7. 《人人都懂的宏观经济》笔记
  8. Internet基础
  9. html轮播图鼠标可以暂停,原装js轮播图,鼠标移入停止轮播,移出继续轮播
  10. 手机端qq客服代码点击弹出qq聊天窗http://www.51xuediannao.com/jiqiao/1026.html