【1++的数据结构】之map与set(二)

👍作者主页:进击的1++
🤩 专栏链接:【1++的数据结构】


文章目录

  • 一,前言
  • 二,红黑树的概念及其性质
  • 三,红黑树的插入
  • 四,红黑树的验证
  • 五,map与set的封装
    • 红黑树迭代器的实现
    • map重载[ ]
    • map的封装代码
    • set的封装代码

一,前言

为什么在这里要讲解红黑树?因为map与set的底层是红黑树,因此在这一节我们要讲红黑树的结构,之后会讲以红黑树为 底层的map与set的封装。

二,红黑树的概念及其性质

什么是红黑树?
红黑树是一种平衡二叉树,其结点结构中多了表示颜色的成员变量(红或黑),红黑树通过结点间颜色匹配的限制,从而能够控制树的最长路径不会超过最短路径的2倍。

红黑树的性质:

  1. 每个结点不是黑色就是红色
  2. 根节点是黑色的
  3. 红节点的两个孩子都是黑色的
  4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,黑色结点数目相同
  5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
    为什么有了上述的几个条件就可以保证最长路径不超过最短路径的二倍呢?
    由于规则三和规则四的限制,导致我们的最长路径一定是一红一黑结点搭配的;而最短结点则是全黑,且由于个规则四,黑色结点数目相同,因此最长路径就不会超过最短路径的二倍了。
    在这里插入图片描述
    红黑树的结点结构与AVL树的结点结构不同之处在于将AVL树结点中的平衡因子变量换为了表示颜色的变量。
enum Colour
{Red,Black
};template<class T>
struct TreeNode
{TreeNode<T>* _parent;TreeNode<T>* _left;TreeNode<T>* _right;Colour _col;T _data;TreeNode(const T& data):_parent(nullptr),_left(nullptr),_right(nullptr),_col(Red),_data(data){}};

并且,我们注意到红黑树新结点的默认颜色是红色?为什么要进行这样的设计呢?
通过观察红黑树的几条性质我们发现,当其默认结点颜色为黑色时,由于规则四,其新结点对这棵树一定会产生影响,而若新节点颜色为红色,则影响会比较小。
在这里插入图片描述

三,红黑树的插入

按照二叉搜索树的插入规则插入这部分我们在前面已经多次讲到,因此本节将不再讲解,最重要的还是其规则被破坏后,进行平衡的这一部分。
由于新插入结点的颜色默认为红色,若其双亲结点为黑色,则不违反任何规则,若其双亲结点为红色,则违反了规则三,需要进行调整。已经有人为我们总结几种调整的情况,我们只需要按照其总结进行调整就行。
几种需要调整的情况如下:

  1. 情况一:在这里插入图片描述
    当cur,p,u为红,g为黑时
    调整方式:将p,u改为黑,g改为红。
    若g为根节点,则需将根节点(g)改为黑色;
    若不是,且g的双亲结点也为红色,则按此方式继续向上调整。直到调整完成或是到达根节点。(注:根节点都得调整为黑色)。

代码如下:

while (parent && parent->_col == Red){Node* grandparent = parent->_parent;assert(grandparent);assert(grandparent->_col == Black);if (grandparent->_left == parent){Node* uncle = grandparent->_right;//情况一if (uncle && uncle->_col == Red){parent->_col=uncle->_col = Black;grandparent->_col = Red;cur = grandparent;parent = grandparent->_parent;		else{Node* uncle = grandparent->_left;//情况一if (uncle && uncle->_col == Red){parent->_col = uncle->_col = Black;grandparent->_col = Red;cur = grandparent;parent = grandparent->_parent;}		}}_root->_col = Black;return make_pair(_iterator(cur), true);}
  1. 情况二:
    在这里插入图片描述
    若cur,p为红,u为黑或者不存在时。

u的情况说明:
若u不存在,则cur一定是新插入的结点,若其不是新插入的结点,则p与cur一定有一个黑色结点,这不符合规则四。
若u存在,则cur一定不是新插入的结点,且原来为黑色,当新插入一个节点后调整为了红色。

若g的左子树为p,p的左子树为cur:调整方式,针对g做右单旋,并且将g改为红色,p改为黑色。
若g的右子树为p,p的右子树为cur:调整方式,针对做左单旋,并且将g改为红色,p改为黑色。
3. 情况三:
在这里插入图片描述
若g的左子树为p,p的右子树为cur,则先针对p做左旋,就和情况二相同了,再进行右旋。
若g的右子树为p,p的左子树为cur,则先针对p做右旋,就和情况二相同了,再进行左旋。

代码如下:

while (parent && parent->_col == Red){Node* grandparent = parent->_parent;assert(grandparent);assert(grandparent->_col == Black);if (grandparent->_left == parent){Node* uncle = grandparent->_right;//情况一if (uncle && uncle->_col == Red){parent->_col=uncle->_col = Black;grandparent->_col = Red;cur = grandparent;parent = grandparent->_parent;}else//情况二+三{if (parent->_left == cur){RotateR(grandparent);grandparent->_col = Red;parent->_col = Black;}else{RotateL(parent);RotateR(grandparent);grandparent->_col = Red;cur->_col = Black;}break;}}else{Node* uncle = grandparent->_left;//情况一if (uncle && uncle->_col == Red){parent->_col = uncle->_col = Black;grandparent->_col = Red;cur = grandparent;parent = grandparent->_parent;}else//情况二+三{if (parent->_right == cur){RotateL(grandparent);grandparent->_col = Red;parent->_col = Black;}else{RotateR(parent);RotateL(grandparent);grandparent->_col = Red;cur->_col = Black;}break;}}}_root->_col = Black;return make_pair(_iterator(cur), true);}

四,红黑树的验证

我们通过验证红黑树的几条性质来验证这棵树是否为红黑树。

  1. 根节点为黑色结点
  2. 各路径的黑色结点数相同
  3. 红色结点的子节点为黑色
    接下来,我们根据这几条规则来写代码判断是否为红黑树
    代码如下:
bool Isbalance() {if (_root == nullptr){return true;}if (_root->_col == Red){cout << "根不是黑色结点" << endl;return false;}int BenchNum = 0;return PrevCheck(_root,0,BenchNum);}bool PrevCheck(Node* root, int BlackNum, int& BenchNum){if (root == nullptr){if (BenchNum == 0){BenchNum = BlackNum;return true;}else{if (BenchNum != BlackNum){cout << "路径黑色结点数量不同" << endl;return false;}elsereturn true;}}if (root->_col == Black){BlackNum++;}if (root->_col == Red && root->_parent->_col == Red){cout << "连续两个红色" << endl;return false;}return PrevCheck(root->_left, BlackNum, BenchNum) && PrevCheck(root->_right, BlackNum, BenchNum);}

五,map与set的封装

map与set的底层都是红黑树,那么如何用同一种底层结构去实现封装两个不同的数据结构呢?
事实上,在红黑树中其类模板为template<class K,class
T>。在map中T为pair<K,V>类型,在set中为K类型,这样就实现了同一底层封装两种不同的数据结构,也就是泛型编程。

红黑树迭代器的实现

红黑树是根据某种规则将一些结点链接起来的结构。因此其迭代器底层也与链表的迭代器一样,是结点指针,其重点是++和–的运算符重载。
通过观察,我们对++的重载有如下总结:平衡二叉搜索树按照中序遍历则是有序的。因此,对于++,则是找中序遍历的下一个结点。中序遍历的规则:左子树–根–右子树。
要找当前结点的下一个,则当其右子树不为空时,其右子树的最左边的结点就是下一个结点。
当右子树为空时,则向上找,直到cur!=parent->right,或parent为空其下一结点就为双亲结点。

代码如下:

Self& operator++(){if (_node->_right){Node* left = _node->_right;while (left->_left){left = left->_left;}_node = left;}else{Node* cur = _node;Node* parent = cur->_parent;while (parent && parent->_right == cur){cur = cur->_parent;parent = cur->_parent;}_node = parent;}return *this;}

–的重载与++相反,就不再详细讲解。
代码如下:

Self& operator--(){if (_node->_left){Node* right = _node->_left;while (right->_right){right = right->_right;}_node = right;}else{Node* cur = _node;Node* parent = cur->_parent;while (parent && parent->_left == cur){cur = cur->_parent;parent = cur->_parent;}_node = parent;}return *this;}

由于在插入时,map与set传入的类型不同,但都需要比较K类型对象,因此,我们在map与set中都写一个用于确定比较元素的类型的仿函数,map中由于T为pair<K,V>,(pair<K,V> kv)则需比较的是kv.first。
而set中的T为K,K key就直接比较key就行。

struct KeyOf{const K& operator() (const pair<K, V>& kv){return kv.first;}};struct KeyOfS{const K& operator()(const K& key){return key;}};

map重载[ ]

map的[ ]功能比较强大,它集查找,插入,修改功能为一体。

V& operator[](const K& key){pair<iterator, bool> ret = Insert(make_pair(key, V()));return ret.first->second;}

若树种有这个结点,则会插入失败,会返回这个结点的迭代器以及false。
在这里插入图片描述
由于重载函数返回的是V的引用,因此此结点的value便可以被修改。
若此结点不存在,则会插入一个V的匿名对象的value值。并返回其引用。

map的封装代码

以下是map的封装代码:

template<class K,class V>class map{struct KeyOf{const K& operator() (const pair<K, V>& kv){return kv.first;}};public:typedef  typename RBTree<K, pair<K, V>, KeyOf>::_iterator iterator;pair<iterator, bool> Insert(const pair<K, V>& kv){return _t.Insert(kv);}V& operator[](const K& key){pair<iterator, bool> ret = Insert(make_pair(key, V()));return ret.first->second;}iterator begin(){return _t.begin();}iterator end(){return _t.end();}private:RBTree<K,pair<K,V>, KeyOf> _t;};

set的封装代码

以下是set的封装代码:

template<class K>class set{struct KeyOfS{const K& operator()(const K& key){return key;}};public:typedef typename RBTree<K, K, KeyOfS>::_iterator iterator;pair<iterator, bool> Insert(const K& key){return _t.Insert(key);}iterator begin(){return _t.begin();}iterator end(){return _t.end();}private:RBTree< K, K, KeyOfS> _t;};

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/129673.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

qt 正则表达式

以上是正则表达式的格式说明 以下是自己写的正则表达式 22-25行 是一种设置正则表达式的方式&#xff0c; 29-34行 : 29行 new一个正则表达式的过滤器对象 30行 正则表达式 的过滤格式 这个格式是0-321的任意数字都可以输入 31行 将过滤格式保存到过滤器对象里面 32行 将验…

快人一步进入智能新纪元,《新程序员006》来了!

文 | 王启隆 曾浩辰 出品 | 《新程序员》编辑部 亲爱的 CSDN 以及《新程序员》的读者朋友们&#xff0c;金秋将至&#xff0c;《新程序员006&#xff1a;人工智能新十年》也正式与大家见面&#xff01;现在点击下方封面&#xff0c;即可订阅&#xff0c;立即阅读电子书。精美…

UNIX网络编程卷一 学习笔记 第三十章 客户/服务器程序设计范式

开发一个Unix服务器程序时&#xff0c;我们本书做过的进程控制&#xff1a; 1.迭代服务器&#xff08;iterative server&#xff09;&#xff0c;它的适用情形极为有限&#xff0c;因为这样的服务器在完成对当前客户的服务前无法处理已等待服务的新客户。 2.并发服务器&#x…

Java笔记040-反射/反射机制、Class类

目录 反射(reflection) 一个需求引出反射 反射机制 Java反射机制原理图 Java反射机制可以完成 反射相关的主要类 反射机制的优点和缺点 反射调用优化-关闭访问检查 Class类 基本介绍 代码解释部分 类加载方法 应用实例&#xff1a;Class02.java 获取Class类对象 …

【17 > 分布式接口幂等性】2. Update的幂等性原理解析

一、 根据 唯一业务号去更新 数据的情况 1.1 原理 1.2 操作 1.3 实战 Stage 1&#xff1a;表添加 version 字段 Stage 2&#xff1a;前端 > 版本号放入隐藏域 Stage 3&#xff1a;后台 > 使用版本号作为更新条件 二、更新操作没有唯一业务号&#xff0c;可使用Tok…

RP9学习-1

一.基础 1.10个面板位置示意图&#xff1a; 2.常用英文 1.鼠标点击&#xff1a;click or tap 3.工作区 1.恢复默认工作区&#xff1a; view-->reset view 2.自定义工作区&#xff1a; 可以用鼠标左键拖动面板到独立的位置或者吸附到其他面板上 3.自定义工具栏 view-->T…

Adobe Acrobat Reader界面改版 - 解决方案

问题 日期&#xff1a;2023年9月 Adobe Acrobat Reader下文简称Adobe PDF Reader&#xff0c;此软件会自动进行更新&#xff0c;当版本更新至2023.003.20284版本后。 软件UI界面会大改版&#xff1a;书签页变成了右边、工具栏变到了左边、缩放按钮变到了右下角&#xff0c;如…

打造高效的私密论坛网站:Cpolar内网穿透+HadSky轻量级搭建指南

文章目录 前言1. 网站搭建1.1 网页下载和安装1.2 网页测试1.3 cpolar的安装和注册 2. 本地网页发布2.1 Cpolar临时数据隧道2.2 Cpolar稳定隧道&#xff08;云端设置&#xff09;2.3 Cpolar稳定隧道&#xff08;本地设置&#xff09;2.4 公网访问测试 总结 前言 经过多年的基础…

怎么激活IDM

IDM是一个下载软件。 激活它需要用到git上面的一个项目&#xff0c;同时网络要能连到github GitHub - lstprjct/IDM-Activation-Script: IDM Activation & Trail Reset Script WINR 输入powershell 输入命令行 iex(irm is.gd/idm_reset) 或者 iwr -useb https://raw.…

vim常用操作

一、Esc键 & 命令模式 1.撤销&#xff1a;u 恢复撤销&#xff1a;Ctrl r 2.定位 行首&#xff1a;0 行尾&#xff1a;$ 第7行&#xff1a;7G 3.编辑 下行开始插入&#xff1a; o 删除行&#xff1a;dd 复制3行并粘贴&#xff1a;3yy ---> p 复制单词并粘贴&#…

【Leetcode-面试经典150题-day22】

目录 97. 交错字符串 97. 交错字符串 题意&#xff1a; 给定三个字符串 s1、s2、s3&#xff0c;请你帮忙验证 s3 是否是由 s1 和 s2 交错 组成的。 两个字符串 s 和 t 交错 的定义与过程如下&#xff0c;其中每个字符串都会被分割成若干 非空 子字符串&#xff1a; s s1 s2 …

Hadoop:HDFS--分布式文件存储系统

目录 HDFS的基础架构 VMware虚拟机部署HDFS集群 HDFS集群启停命令 HDFS Shell操作 hadoop 命令体系&#xff1a; 创建文件夹 -mkdir 查看目录内容 -ls 上传文件到hdfs -put 查看HDFS文件内容 -cat 下载HDFS文件 -get 复制HDFS文件 -cp 追加数据到HDFS文件中 -appendTo…

初阶扫雷(超详解)

✨博客主页&#xff1a;小钱编程成长记 &#x1f388;博客专栏&#xff1a;C语言小游戏 &#x1f388;推荐相关博文&#xff1a;初阶三子棋&#xff08;超详解&#xff09; 初阶扫雷 1.游戏介绍2.基本思路3.实现前的准备4.实现步骤4.1 打印菜单4.2 初始化扫雷棋盘4.3 打印扫雷棋…

如何让Android平台像网络摄像机一样实现GB28181前端设备接入?

技术背景 好多开发者在做国标对接的时候&#xff0c;首先想到的是IPC&#xff08;网络摄像头&#xff09;&#xff0c;通过参数化配置&#xff0c;接入到国标平台&#xff0c;实现媒体数据的按需查看等操作。 像执法记录仪等智能终端&#xff0c;跑在Android平台&#xff0c;…

2024腾讯校招后端面试真题汇总及其解答(三)

21【算法题】反转链表 题目: 给定单链表的头节点 head ,请反转链表,并返回反转后的链表的头节点。 示例 1: 输入:head = [1,2,3,4,5] 输出:[5,4,3,2,1]示例 2: 输入:head = [1,2] 输出:[2,1]示例 3: 输入:head = [] 输出:[]提示: 链表中节点的数目范围是 [0, 5…

Spring系列文章:Bean的获取⽅式

一、简介 Spring为Bean提供了多种实例化⽅式&#xff0c;通常包括4种⽅式。&#xff08;也就是说在Spring中为Bean对象的创建准 备了多种⽅案&#xff0c;⽬的是&#xff1a;更加灵活&#xff09; 第⼀种&#xff1a;通过构造⽅法实例化 第⼆种&#xff1a;通过简单⼯⼚模式…

App测试时常用的adb命令你都掌握了哪些呢?

adb 全称为 Android Debug Bridge&#xff08;Android 调试桥&#xff09;&#xff0c;是 Android SDK 中提供的用于管理 Android 模拟器或真机的工具。 adb 是一种功能强大的命令行工具&#xff0c;可让 PC 端与 Android 设备进行通信。adb 命令可执行各种设备操作&#xff0…

Redis原理:动态字符串SDS

&#xff08;课程总结自b站黑马程序员课程&#xff09; 一、引言 Redis中保存的Key是字符串&#xff0c;value往往是字符串或者字符串的集合。可见字符串是Redis中最常用的一种数据结构。 不过Redis没有直接使用C语言中的字符串&#xff0c;因为C语言字符串存在很多问题&…

防火墙 FireWall

这里写自定义目录标题 一、概述二、防火墙分类三、防火墙性能四、硬件防火墙定义五、硬件防火墙作用&#xff08;拓扑图 ups&#xff09;六、硬件防火墙品牌七、软件防火墙八、iptables一、iptables是什么&#xff1f;二、netfilter/iptables功能三、iptables概念四、iptables中…

【云原生】Kubeadmin安装k8s集群

目录 前言&#xff1a; 一 环境部署 1.1 服务器部署功能 1.2 环境准备&#xff08;所有节点&#xff09; 二 安装docker&#xff08;所有节点&#xff09; 三 所有节点安装kubeadm&#xff0c;kubelet和kubectl 3.1 定义kubernetes源 3.2 开机自启kubelet 四 部署K8S集…