STL源码剖析---红黑树原理详解上

article/2025/9/14 0:37:24

转载请标明出处,原文地址:http://blog.csdn.net/hackbuteer1/article/details/7740956
一、红黑树概述

     红黑树和我们以前学过的AVL树类似,都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。不过自从红黑树出来后,AVL树就被放到了博物馆里,据说是红黑树有更好的效率,更高的统计性能。这一点在我们了解了红黑树的实现原理后,就会有更加深切的体会。
     红黑树和AVL树的区别在于它使用颜色来标识结点的高度,它所追求的是局部平衡而不是AVL树中的非常严格的平衡。学过数据结构的人应该都已经领教过AVL树的复杂,但AVL树的复杂比起红黑树来说简直是小巫见大巫,红黑树才是真正的变态级数据结构。
     由于STL中的关联式容器默认的底层实现都是红黑树,因此红黑树对于后续学习STL源码还是很重要的,有必要掌握红黑树的实现原理和源码实现。
     红黑树是AVL树的变种,红黑树通过一些着色法则确保没有一条路径会比其它路径长出两倍,因而达到接近平衡的目的。所谓红黑树,不仅是一个二叉搜索树,而且必须满足一下规则:
     1、每个节点不是红色就是黑色。
     2、根节点为黑色。
     3、如果节点
为红色,其子节点必须为黑色
     4、任意一个节点到到NULL(树尾端)的任何路径,所含之黑色节点数必须相同。

上面的这些约束保证了这个树大致上是平衡的,这也决定了红黑树的插入、删除、查询等操作是比较快速的。 根据规则4,新增节点必须为红色;根据规则3,新增节点之父节点必须为黑色。当新节点根据二叉搜索树的规则到达其插入点时,却未能符合上述条件时,就必须调整颜色并旋转树形,如下图:

假设我们为上图分别插入节点3、8、35、75,根据二叉搜索树的规则,插入这四个节点后,我们会发现它们都破坏了红黑树的规则,因此我们必须调整树形,也就是旋转树形并改变节点的颜色。

二、红黑树上结点的插入

      在讨论红黑树的插入操作之前必须要明白,任何一个即将插入的新结点的初始颜色都为红色。这一点很容易理解,因为插入黑点会增加某条路径上黑结点的数目,从而导致整棵树黑高度的不平衡。但如果新结点的父结点为红色时(如下图所示),将会违反红黑树的性质:一条路径上不能出现相邻的两个红色结点。这时就需要通过一系列操作来使红黑树保持平衡。

      为了清楚地表示插入操作以下在结点中使用“新”字表示一个新插入的结点;使用“父”字表示新插入点的父结点;使用“叔”字表示“父”结点的兄弟结点;使用“祖”字表示“父”结点的父结点。插入操作分为以下几种情况:
1、黑父
     如下图所示,如果新节点的父结点为黑色结点,那么插入一个红点将不会影响红黑树的平衡,此时插入操作完成。红黑树比AVL树优秀的地方之一在于黑父的情况比较常见,从而使红黑树需要旋转的几率相对AVL树来说会少一些。

2、红父
     如果新节点的父结点为红色,这时就需要进行一系列操作以保证整棵树红黑性质。如下图所示,由于父结点为红色,此时可以判定,祖父结点必定为黑色。这时需要根据叔父结点的颜色来决定做什么样的操作。青色结点表示颜色未知。由于有可能需要根结点到新点的路径上进行多次旋转操作,而每次进行不平衡判断的起始点(我们可将其视为新点)都不一样。所以我们在此使用一个蓝色箭头指向这个起始点,并称之为判定点。

2.1 红叔
当叔父结点为红色时,如下图所示,无需进行旋转操作,只要将父和叔结点变为黑色,将祖父结点变为红色即可。但由于祖父结点的父结点有可能为红色,从而违反红黑树性质。此时必须将祖父结点作为新的判定点继续向上(迭代)进行平衡操作。

需要注意的是,无论“父节点”在“叔节点”的左边还是右边,无论“新节点”是“父节点”的左孩子还是右孩子,它们的操作都是完全一样的(其实这种情况包括4种,只需调整颜色,不需要旋转树形)。
2.2 黑叔
当叔父结点为黑色时,需要进行旋转,以下图示了所有的旋转可能:
Case 1:

Case 2:

Case 3:

Case 4:


      可以观察到,当旋转完成后,新的旋转根全部为黑色,此时不需要再向上回溯进行平衡操作,插入操作完成。需要注意,上面四张图的“叔”、“1”、“2”、“3”结点有可能为黑哨兵结点。
      其实红黑树的插入操作不是很难,甚至比AVL树的插入操作还更简单些。红黑树的插入操作源代码如下:
// 元素插入操作  insert_unique()
// 插入新值:节点键值不允许重复,若重复则插入无效
// 注意,返回值是个pair,第一个元素是个红黑树迭代器,指向新增节点
// 第二个元素表示插入成功与否
template<class Key , class Value , class KeyOfValue , class Compare , class Alloc>
pair<typename rb_tree<Key , Value , KeyOfValue , Compare , Alloc>::iterator , bool>
rb_tree<Key , Value , KeyOfValue , Compare , Alloc>::insert_unique(const Value &v)
{rb_tree_node* y = header;    // 根节点root的父节点rb_tree_node* x = root();    // 从根节点开始bool comp = true;while(x != 0){y = x;comp = key_compare(KeyOfValue()(v) , key(x));    // v键值小于目前节点之键值?x = comp ? left(x) : right(x);   // 遇“大”则往左,遇“小于或等于”则往右}// 离开while循环之后,y所指即插入点之父节点(此时的它必为叶节点)iterator j = iterator(y);     // 令迭代器j指向插入点之父节点yif(comp)     // 如果离开while循环时comp为真(表示遇“大”,将插入于左侧){if(j == begin())    // 如果插入点之父节点为最左节点return pair<iterator , bool>(_insert(x , y , z) , true);else     // 否则(插入点之父节点不为最左节点)--j;   // 调整j,回头准备测试}if(key_compare(key(j.node) , KeyOfValue()(v) ))// 新键值不与既有节点之键值重复,于是以下执行安插操作return pair<iterator , bool>(_insert(x , y , z) , true);// 以上,x为新值插入点,y为插入点之父节点,v为新值// 进行至此,表示新值一定与树中键值重复,那么就不应该插入新值return pair<iterator , bool>(j , false);
}// 真正地插入执行程序 _insert()
template<class Key , class Value , class KeyOfValue , class Compare , class Alloc>
typename<Key , Value , KeyOfValue , Compare , Alloc>::_insert(base_ptr x_ , base_ptr y_ , const Value &v)
{// 参数x_ 为新值插入点,参数y_为插入点之父节点,参数v为新值link_type x = (link_type) x_;link_type y = (link_type) y_;link_type z;// key_compare 是键值大小比较准则。应该会是个function objectif(y == header || x != 0 || key_compare(KeyOfValue()(v) , key(y) )){z = create_node(v);    // 产生一个新节点left(y) = z;           // 这使得当y即为header时,leftmost() = zif(y == header){root() = z;rightmost() = z;}else if(y == leftmost())     // 如果y为最左节点leftmost() = z;          // 维护leftmost(),使它永远指向最左节点}else{z = create_node(v);        // 产生一个新节点right(y) = z;              // 令新节点成为插入点之父节点y的右子节点if(y == rightmost())rightmost() = z;       // 维护rightmost(),使它永远指向最右节点}parent(z) = y;      // 设定新节点的父节点left(z) = 0;        // 设定新节点的左子节点right(z) = 0;       // 设定新节点的右子节点// 新节点的颜色将在_rb_tree_rebalance()设定(并调整)_rb_tree_rebalance(z , header->parent);      // 参数一为新增节点,参数二为根节点root++node_count;       // 节点数累加return iterator(z);  // 返回一个迭代器,指向新增节点
}// 全局函数
// 重新令树形平衡(改变颜色及旋转树形)
// 参数一为新增节点,参数二为根节点root
inline void _rb_tree_rebalance(_rb_tree_node_base* x , _rb_tree_node_base*& root)
{x->color = _rb_tree_red;    //新节点必为红while(x != root && x->parent->color == _rb_tree_red)    // 父节点为红{if(x->parent == x->parent->parent->left)      // 父节点为祖父节点之左子节点{_rb_tree_node_base* y = x->parent->parent->right;    // 令y为伯父节点if(y && y->color == _rb_tree_red)    // 伯父节点存在,且为红{x->parent->color = _rb_tree_black;           // 更改父节点为黑色y->color = _rb_tree_black;                   // 更改伯父节点为黑色x->parent->parent->color = _rb_tree_red;     // 更改祖父节点为红色x = x->parent->parent;}else    // 无伯父节点,或伯父节点为黑色{if(x == x->parent->right)   // 如果新节点为父节点之右子节点{x = x->parent;_rb_tree_rotate_left(x , root);    // 第一个参数为左旋点}x->parent->color = _rb_tree_black;     // 改变颜色x->parent->parent->color = _rb_tree_red;_rb_tree_rotate_right(x->parent->parent , root);    // 第一个参数为右旋点}}else          // 父节点为祖父节点之右子节点{_rb_tree_node_base* y = x->parent->parent->left;    // 令y为伯父节点if(y && y->color == _rb_tree_red)    // 有伯父节点,且为红{x->parent->color = _rb_tree_black;           // 更改父节点为黑色y->color = _rb_tree_black;                   // 更改伯父节点为黑色x->parent->parent->color = _rb_tree_red;     // 更改祖父节点为红色x = x->parent->parent;          // 准备继续往上层检查}else    // 无伯父节点,或伯父节点为黑色{if(x == x->parent->left)        // 如果新节点为父节点之左子节点{x = x->parent;_rb_tree_rotate_right(x , root);    // 第一个参数为右旋点}x->parent->color = _rb_tree_black;     // 改变颜色x->parent->parent->color = _rb_tree_red;_rb_tree_rotate_left(x->parent->parent , root);    // 第一个参数为左旋点}}}//whileroot->color = _rb_tree_black;    // 根节点永远为黑色
}// 左旋函数
inline void _rb_tree_rotate_left(_rb_tree_node_base* x , _rb_tree_node_base*& root)
{// x 为旋转点_rb_tree_node_base* y = x->right;          // 令y为旋转点的右子节点x->right = y->left;if(y->left != 0)y->left->parent = x;           // 别忘了回马枪设定父节点y->parent = x->parent;// 令y完全顶替x的地位(必须将x对其父节点的关系完全接收过来)if(x == root)    // x为根节点root = y;else if(x == x->parent->left)         // x为其父节点的左子节点x->parent->left = y;else                                  // x为其父节点的右子节点x->parent->right = y;y->left = x;x->parent = y;
}// 右旋函数
inline void _rb_tree_rotate_right(_rb_tree_node_base* x , _rb_tree_node_base*& root)
{// x 为旋转点_rb_tree_node_base* y = x->left;          // 令y为旋转点的左子节点x->left = y->right;if(y->right != 0)y->right->parent = x;           // 别忘了回马枪设定父节点y->parent = x->parent;// 令y完全顶替x的地位(必须将x对其父节点的关系完全接收过来)if(x == root)root = y;else if(x == x->parent->right)         // x为其父节点的右子节点x->parent->right = y;else                                  // x为其父节点的左子节点x->parent->left = y;y->right = x;x->parent = y;
}

转载请标明出处,原文地址:http://blog.csdn.net/hackbuteer1/article/details/7740956

http://chatgpt.dhexx.cn/article/4ud1Sgqq.shtml

相关文章

C++ STL源码剖析 笔记

写在前面 记录一下《C STL源码剖析》中的要点。 一、STL六大组件 容器&#xff08;container&#xff09;&#xff1a; 各种数据结构&#xff0c;用于存放数据&#xff1b;class template 类泛型&#xff1b;如vector, list, deque, set, map&#xff1b; 算法&#xff08;a…

STL(C++标准库,体系结构及其内核分析)(STL源码剖析)(更新完毕)

文章目录 介绍Level 0&#xff1a;使用C标准库0 STL六大部件0.1 六大部件之间的关系0.2 复杂度0.3 容器是前闭后开&#xff08;左闭右开&#xff09;区间 1 容器的结构与分类1.1 使用容器Array1.2 使用容器vector1.3 使用容器list1.4 使用容器foward_list1.5 使用容器slist1.6 …

STL源码详解

STL详解 STL介绍空间配置器一级空间配置器二级空间配置器 序列式容器vectorlistdeque 适配器stackqueueheappriority_queue 关联式容器setmultisetmapmultimap 非标准容器hash_set&#xff08;unordered_set&#xff09;hash_multiset&#xff08;unordered_multiset&#xff0…

STL源码刨析

1. STL概述 STL起源&#xff1a; 为的就是复用性的提升&#xff0c;减少人力资源的浪费&#xff0c;建立了数据结构与算法的一套标准。 STL所实现的、是依据泛型思维架设起来的一个概念结构。这个以抽象概念〔 abstract concepts&#xff09;为主体而非以实际类(classes&…

侯捷——STL源码剖析 笔记

侯捷——STL源码剖析 笔记 1.总览 1.STL六大部件之间的关系 在下图中&#xff0c;我们使用了如下&#xff1a; 1.一个容器vector 2.使用vector时&#xff0c;使用分配器分配内存 3.使用vi.begin(),vi.end()即迭代器&#xff0c;作为算法的参数 4.使用count_if算法 5.使用仿函…

【GeoServer】CentOS7.x上GeoServer的安装部署

GeoServer 是 OpenGIS Web 服务器规范的 J2EE 实现&#xff0c;利用 GeoServer 可以方便的发布地图数据&#xff0c;允许用户对特征数据进行更新、删除、插入操作&#xff0c;通过 GeoServer 可以比较容易的在用户之间迅速共享空间地理信息。 GeoServer 主要特性&#xff1a;兼…

部署GeoServer

部署GeoServer 部署方式很多总&#xff0c;这里介绍两种 安装包安装 默认已经安装了Tomcat&#xff1a; Tomcat9.0安装教程 下载war包 使用geoserver的war包在tomcat中部署&#xff0c;从官网中下载对应版本的war GeoServer官网地址 安装 解压软件 将war包复制到tomcat…

GeoServer安装部署

介绍&#xff1a; Geoserver 是一个开源的地理空间数据服务器,它可以发布和编辑地理数据。这里简单介绍 Geoserver 的部署安装和后台运行。 它的主要功能包括: 管理空间数据&#xff1a;GeoServer可以连接各种空间数据源,包括文件(SHP、CSV等)、数据库(PostGIS,Oracle,SQL Ser…

geoserver 创建只读用户

目录 一、创建只读角色 一、创建新账号&#xff0c;将新账号添加到只读角色中 三、配置权限 四、校验 一、创建只读角色 1、选择Security->Users,Groups,Roles->Roles->Add new role 2、输入名称&#xff0c;parent role 不选&#xff08;防止获取到父级角色的权限…

GeoServer学习笔记-01GeoSever运行编译

一、运行 1. 下载GeoServer GitHub仓库地址&#xff1a;https://github.com/geoserver/geoserver 2.本地代码工具打开项目 在idea里&#xff0c;文件->新建->来自现有的源代码项目&#xff0c;选择项目的pom文件加载项目。 3.idea编译环境设置 &#xff08;1&#xff09;…

java geoserver_本机搭建GeoServer

最近尝试试本机搭建GeoSrver的服务&#xff0c;分享一下搭建安装教程&#xff0c;总共分为以下几步&#xff1a; 下载Java的GDK&#xff0c;添加环境变量 GeoServer 依赖于Java的环境&#xff0c;劝告一定要下载 1.8(8)的版本&#xff0c;虽然现在已经更新到 14&#xff0c;但是…

Geoserver中跨域问题解决

场景 GeoServer简介、下载、配置启动、发布shapefile全流程(图文实践)&#xff1a; GeoServer简介、下载、配置启动、发布shapefile全流程(图文实践)_霸道流氓气质的博客-CSDN博客 上面安装Geoserver的基础下。 使用ajax请求GeoJson时提示跨域 注&#xff1a; 博客&#x…

GeoServer发布服务,中文标注乱码

1.问题&#xff1a; 发布的矢量数据源 shapefiles&#xff0c;中文标注显示乱码问题&#xff0c;如下图所示&#xff1a; 2.解决办法 编辑矢量数据源&#xff0c;DBF文件的字符集&#xff0c;改为GB2312。 显示正常&#xff1a;

geoserver热图

1.参考 GeoServer发布Heatmap - wenglabs - 博客园 Rendering Transformations — GeoServer 2.21.x User Manual 2.下载 GeoServer 及wps插件&#xff0c;该插件gs:heatmap支持热图样式 3.发布测试shp geoserver热图测试数据-其它文档类资源-CSDN下载 4、添加热图样式&…

Geoserver添加mongoDB数据源

文章目录 概述操作1. 添加mongodb 插件2. 添加数据源3. 添加数据3. 发布服务 概述 本文讲述如何在geoserver中添加mongoDB作为数据源&#xff0c;并发布图层。 操作 1. 添加mongodb 插件 在浏览器输入地址下载页面&#xff0c;下载mongodb插件。 [外链图片转存失败,源站可能…

Geoserver介绍2:geoserver页面介绍

目录 Geoserver介绍2&#xff1a;geoserver页面介绍 一、打开登录geoserver的web管理页面 二、 页面左侧&#xff0c;功能介绍 &#xff08;一&#xff09;、关于和状态 &#xff08;二&#xff09;、数据 1、图层预览 2、工作区 3、数据存储 4、图层 5、图层组 6、样…

geoserver

geoserver 总 —— 配置建议数据源选择QGIS配色相关透明度设置 安装配置Windowsjdk环境配置geoserver安装安装一体化包&#xff08;基于 jetty 推荐&#xff09;基于tomcat安装 Linux&#xff08;centos7.9&#xff09;基于 tomcat 安装 geoserver性能调优JVM内存调整启用 CORS…

geoserver离线地图服务搭建和图层发布

前言 项目用到了GIS地图&#xff0c;在浏览器进行展示。起初使用了在线的高德地图。高德官网api丰富&#xff0c;且都是中文&#xff0c;很好用&#xff0c;也很方便。但是随着需求的变更&#xff0c;项目环境也从互联网变成了内网环境。所以高德地图就不能再用了&#xff0c;…

GIS系列(四)GeoServer的介绍和用法

《WebGIS快速开发教程》写好啦_WebGIS小智的博客-CSDN博客 首先,GeoServer是一个地图服务器。 关于地图服务器,其实和普通服务器没啥区别,就是专门用来发布地图的。 实际上,如果你的项目是前后端结合的话,可以不需要地图服务器。 你可以在后端配合Geotools,postgis等…

如何使用GeoServer发布WMS服务

如何使用GeoServer发布地图 作者&#xff1a;郜庆科 本文所采用的系统为Windows 10 64bit操作系统&#xff0c;使用FireFox浏览器 一、安装配置Java的SDK 1、 安装Java Development Kit (JDK) 8&#xff0c;java开发环境&#xff0c;需要先到Java的官方网站下载合适自己的安…