map,set的封装(基于改造红黑树)

news/2024/5/9 22:25:02

 目录

引言

1.迭代器

2.map的[]重载

3.KeyOfValue模板参数

4.整体代码展示

 


  

//改造后的红黑树代码
#include <iostream>
using namespace std;enum Colour {RED = 0,BLACK,
};//为了实现map与set封装使用同一个模板红黑树,前者的value是pair,后者的value为key
//因此我们需要对红黑树的模板进行改造
template<class T>
struct RBTreeNode {RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;T _data;Colour _col;//节点构造函数RBTreeNode(const T& data):_left(nullptr), _right(nullptr), _parent(nullptr), _data(data), _col(RED){}
};//树的迭代器构造
template<class T, class Ref, class Ptr>
struct __RBtree_iterator
{typedef RBTreeNode<T> Node;typedef __RBtree_iterator<T, Ref, Ptr> self;Node* _node;__RBtree_iterator() {}__RBtree_iterator(Node* node):_node(node){}// 1、typedef __RBTreeIterator<T, T&, T*> iterator;  拷贝构造// 2、 typedef __RBTreeIterator<T, const T&, const T*> const_iterator;//  支持普通迭代器构造const迭代器的构造函数__RBtree_iterator(const __RBtree_iterator<T, T&, T*>& it):_node(it._node){}//实现前置++,保证指针往后移动,能够实现树的中序遍历self& operator++(){//如果当前节点的右子树不为空if (_node->_right){// 1、右不为空,下一个就是右子树的最左节点Node* subLeft = _node->_right;while (subLeft->_left){subLeft = subLeft->_left;}_node = subLeft;}
//如果当前节点的右子树为空,由于按照左子树,根,右子树遍历,说明此时该子树已经全部遍历//需要向上找孩子为父亲左孩子的祖先节点else{Node* parent = _node->_parent;Node* cur = _node;while (parent && cur == parent->_right){cur = parent;parent = parent->_parent;}_node = parent;}return *this;}//实现前置--self& operator--(){//如果当前节点的左子树不为空if (_node->_left){// 1、左不为空,下一个就是左子树的最右节点Node* subRight = _node->_left;while (subRight->_right){subRight = subRight->_right;}_node = subRight;}// 2、左为空,孩子是父亲的右的那个祖先else{Node* parent = _node->_parent;Node* cur = _node;while (parent && cur == parent->_right){cur = parent;parent = parent->_parent;}_node = parent;}return *this;}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}bool operator!=(const self& s){return _node != s._node;}
};//仿函数
template<class K, class T, class KeyOfValue>
class RBTree
{typedef RBTreeNode<T> Node;
public:~RBTree(){_Destroy(_root);_root = nullptr;}public:typedef __RBtree_iterator<T, T&, T*> iterator;typedef __RBtree_iterator<T, const T&, const T*> const_iterator;iterator begin(){Node* cur = _root;//找树的最左节点while (cur && cur->_left){cur = cur->_left;}return iterator(cur);}const_iterator begin() const{Node* cur = _root;//找树的最左节点while (cur && cur->_left){cur = cur->_left;}return iterator(cur);}iterator end(){return iterator(nullptr);}const_iterator end() const{return const_iterator(nullptr);}//对于set类型而言,其value比较的是key,所以增加了一个模板参数Node* Find(const K& key){Node* cur = _root;KeyOfValue kot;while (cur){   if (kot(cur-> _data) > key){cur = cur->_left;}else if (kot(cur-> _data) < key){cur = cur->_right;}else{return cur;}}return nullptr;}pair<iterator,bool> Insert(const T& data){//假如刚开始没有节点,直接使其成为根即可//满足规则,根节点必须为黑色if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;return make_pair(iterator(_root), true);}//同样满足二叉树的搜索规则,先找到新节点的正确位置Node* parent = nullptr;Node* cur = _root;KeyOfValue kot;while (cur){if (kot(cur-> _data) > kot(data)){parent = cur;cur = cur->_left;}else if (kot(cur-> _data) < kot(data)){parent = cur;cur = cur->_right;}else{return make_pair(iterator(cur),false);}}//建新节点cur = new Node(data);Node* newnode = cur;if (kot(parent->_data) > kot(data)){parent->_left = cur;}else{parent->_right = cur;}cur->_parent = parent;//调整节点颜色while (parent && parent->_col == RED){//找爷爷Node* grandfather = parent->_parent;//父亲为爷爷的左节点if (grandfather->_left == parent){//则叔叔是爷爷的右节点Node* uncle = grandfather->_right;// 情况1:u存在且为红,变色处理,并继续往上处理if (uncle && uncle->_col == RED){//父亲和叔叔节点都调节为黑色parent->_col = BLACK;uncle->_col = BLACK;//爷爷调节为红色grandfather->_col = RED;//往上调节cur = grandfather;parent = cur->_parent;}else // 情况2+3:u不存在/u存在且为黑,旋转+变色{//     g//   p   u// c//右单旋if (cur == parent->_left){RotateR(grandfather);grandfather->_col = RED;parent->_col = BLACK;}else{//    g//   p   u//      c//LR双旋RotateL(parent);RotateR(grandfather);cur->_col = BLACK;//parent->_col = RED;grandfather->_col = RED;}break;}}//父亲为爷爷的右节点else{//则叔叔是爷爷的左节点Node* uncle = grandfather->_left;// 情况1:u存在且为红,变色处理,并继续往上处理if (uncle && uncle->_col == RED){//父亲和叔叔节点都调节为黑色parent->_col = BLACK;uncle->_col = BLACK;//爷爷调节为红色grandfather->_col = RED;//往上调节cur = grandfather;parent = cur->_parent;}else // 情况2+3:u不存在/u存在且为黑,旋转+变色{//     g//   u   p//        c//左单旋if (cur == parent->_right){RotateL(grandfather);grandfather->_col = RED;parent->_col = BLACK;}else{//     g//   u   p//     c//RL双旋RotateR(parent);RotateL(grandfather);cur->_col = BLACK;//parent->_col = RED;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return make_pair(iterator(newnode),true);}int Height(){return _Height(_root);}bool IsRBTree(){//假如根节点存在,但颜色不是黑色,则不是红黑树if (_root && _root->_col == RED){cout << "根节点颜色是红色" << endl;return false;}//随便选一条路径作为黑色节点参考点int benchmark = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK)benchmark++;cur = cur->_left;}// 连续红色节点return _Check(_root, 0, benchmark);}private:void _Destroy(Node* root){if (root == nullptr){return;}_Destroy(root->_left);_Destroy(root->_right);delete root;}int _Height(Node* root){if (root == NULL)return 0;int leftH = _Height(root->_left);int rightH = _Height(root->_right);return leftH > rightH ? leftH + 1 : rightH + 1;}bool _Check(Node* root, int blackNum, int benchmark){//假如到空节点(叶子节点),说明已经走完一条路径,可以开始判断if (root == nullptr){//假如统计出的黑色节点个数和参考黑色节点个数不同,则一定不是红黑树if (blackNum != benchmark){cout << "某条路径黑色节点的数量不相等" << endl;return false;}return true;}//递归遇到黑色节点时,则blackNum可以加1if (root->_col == BLACK)blackNum++;//假如连续存在两个红色节点,则也不是红黑树,注意还需要判断父节点是否存在if (root->_col == RED && root->_parent && root->_parent->_col == RED){cout << "存在连续的红色节点" << endl;return false;}//递归判断是否是红黑树,左子树和右子树都为红黑树,则为红黑树return _Check(root->_left, blackNum, benchmark)&& _Check(root->_right, blackNum, benchmark);}//左旋void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;//b变成30的右parent->_right = subRL;//父节点也需要调整,但subRL可能为空if (subRL)subRL->_parent = parent;//调整时未必是整棵树的调整,所以还需要考虑parent的链接问题,因此需要先记录ppNodeNode* ppNode = parent->_parent;subR->_left = parent;parent->_parent = subR;if (ppNode == nullptr){_root = subR;_root->_parent = nullptr;}else{//在调整爷爷节点指向的时候,还需要考虑原来parent是爷爷的左还是右//subR重新链接回爷爷的左或者右if (ppNode->_right == parent){ppNode->_right = subR;}else{ppNode->_left = subR;}subR->_parent = ppNode;}}//右旋void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;//b变成60的左parent->_left = subLR;//父节点也需要调整,但subRL可能为空if (subLR)subLR->_parent = parent;//调整时未必是整棵树的调整,所以还需要考虑parent的链接问题,因此需要先记录ppNodeNode* ppNode = parent->_parent;subL->_right = parent;parent->_parent = subL;if (ppNode == nullptr){_root = subL;_root->_parent = nullptr;}else{//在调整爷爷节点指向的时候,还需要考虑原来parent是爷爷的左还是右//subL重新链接回爷爷的左或者右if (ppNode->_right == parent){ppNode->_right = subL;}else{ppNode->_left = subL;}subL->_parent = ppNode;}}void _Inorder(Node* root){if (root == nullptr){return;}_Inorder(root->_left);cout << root->_kv.first << " ";_Inorder(root->_right);}
private:Node* _root = nullptr;
};

引言

在上节,我们根据源码,对红黑树代码进行了改造,在这基础上,我们便可以利用同一份红黑树代

码,模拟实现map,set的封装

1.迭代器

set对应key模型,即在不在

一旦插入后,我们是不能够通过迭代器对它的key随意进行修改的

//源码中set的代码
typedef typename rep_type::const_iterator iterator;
typedef typename rep_type::const_iterator const_iterator;

 源码实现不能通过迭代器修改key值得方式也非常直接明了,即无论是const迭代器或者普通迭代 

器,对于set来说,实际上都是const迭代器,我们无法通过const迭代器对key进行修改

与之对应,map则可以通过迭代器修改value值

//源码中map的代码
typedef typename rep_type::iterator iterator;
typedef typename rep_type::const_iterator const_iterator;

对于map来说,是存在const迭代器和普通迭代器的区别的

2.map的[]重载

在map中,有一个元素访问操作非常特殊,也非常方便,我们模拟实现不能错过,那就是[]重载 

它主要能发挥四大作用

第一.(插入)如果红黑树中没有对应的值Key,它可以直接插入

第二.(插入+修改)如果红黑树中不存在对应的键值Key,它可以直接插入,并修改对应的Value 

第三.(查找)直接判断红黑树中是否存在对应的键值Key

第四.(修改)如果红黑树中存在对应的Key,可以直接修改对应的Value

using namespace std;
int main()
{   std::map<char, std::string> mymap;mymap['a'] = "an element";        //插入+修改mymap['b'] = "another element";   mymap['a'] = "an element!";        //修改mymap['c'] = mymap['b']; //赋值std::cout << "mymap['a'] is " << mymap['a'] << '\n';std::cout << "mymap['b'] is " << mymap['b'] << '\n';std::cout << "mymap['c'] is " << mymap['c'] << '\n';std::cout << "mymap['d'] is " << mymap['d'] << '\n';  //插入std::cout << mymap['a'] << endl;  //查找std::cout << "mymap now contains " << mymap.size() << " elements.\n";return 0;
}

对应的代码允许结果如下:

 

想要实现相应[ ]运算符重载,首先需要对insert进行改造

insert函数的返回值,不再是bool值,而是pair,包含迭代器和bool两大部分

假如插入失败,除了返回false外,还需要返回树中已经存在相同键值的迭代器

假如插入成功,除了返回true外,还需要返回插入的新节点的迭代器

通过迭代器,我们就可以相应访问节点的值,如果加一个引用,就可以对其进行修改

//方括号重载
V& operator[](const K& key)
{pair<iterator,bool> ret = _t.Insert(make_pair(key,V()));//插入后,返回该位置对应的迭代器,我们需要返回的是return ret.first->second;
}pair<iterator, bool> insert(const pair<const K, V>& kv)
{return _t.Insert(kv);
}

3.KeyOfValue模板参数

在红黑树插入insert,查找Find中,我们都需要比较两个键值的大小,从而找到对应节点的位置

但是set,map的data值可并不相同,前者是key,后者则是pair

如果直接和key比较,假如是set则没有问题,但是假如是map,则会出现毛病

原因在于pair的比较规则不符合我们的要求,pair 在first member(第一个参数)相同的情况下,还会

去比较second member(第二个参数),这显然不太符合我们要求,因为我们红黑树建树,是只比较

Key的

所以,我们引入了KeyOfValue这个模板参数,然后在map,set封装时,提供对应的仿函数,取出对

应应该比较的值

class map {struct KeyOfValue{const K& operator()(const pair<const K,V>& kv){return kv.first;}};
...
}class set {struct KeyOfValue {const K& operator()(const K& key){return key;}};
...
}

4.整体代码展示

//set模拟封装
#include "Tree.h"
namespace zzq 
{template<class K>class set {struct KeyOfValue {const K& operator()(const K& key){return key;}};public:typedef typename RBTree<K, K, KeyOfValue>::const_iterator iterator;typedef typename RBTree<K, K, KeyOfValue>::const_iterator const_iterator;iterator begin(){return _t.begin();}iterator end(){return _t.end();}const_iterator begin()const{return _t.begin();}const_iterator end()const{return _t.end();}pair<iterator, bool> insert(const K& key){return _t.Insert(key);}private:RBTree<K, K, KeyOfValue> _t;};
}
//map模拟封装
#include "Tree.h"
namespace zzq
{template<class K,class V>class map {struct KeyOfValue{const K& operator()(const pair<const K,V>& kv){return kv.first;}};public:typedef typename RBTree<K, pair<const K, V>, KeyOfValue>::iterator iterator;iterator begin(){return _t.begin();}iterator end(){return _t.end();}const_iterator begin()const{return _t.begin();}const_iterator end()const{return _t.end();}//方括号重载V& operator[](const K& key){pair<iterator,bool> ret = _t.Insert(make_pair(key,V()));//插入后,返回该位置对应的迭代器,我们需要返回的是return ret.first->second;}pair<iterator, bool> insert(const pair<const K, V>& kv){return _t.Insert(kv);}private:RBTree<K, pair<const K, V>, KeyOfValue> _t;};}


http://www.mrgr.cn/p/11205511

相关文章

【玩转Python系列【小白必看】Python多线程爬虫:下载表情包网站的图片

文章目录 前言1. 导入模块和库2. 定义函数 download_image(url, filepath)3. 定义函数 get_page()4. 主程序入口 完整代码运行效果 结束语 前言 本文主要介绍了使用Python编写的多线程爬虫程序&#xff0c;用于下载表情包网站上的图片。通过解析网页内容和使用XPath定位&#x…

python爬虫基础

文章目录 前言爬虫简介urllib库的使用如何获取网页的源码一个类型六个方法一个类型六个方法1、read()方法2、readline()方法3、readlines()方法4、getcode()5、geturl()6、getheaders() urllib下载下载网页下载图片下载视频 请求对象的定制 未完待续 前言 爬虫爬的好牢饭吃的早…

关于K8s的Pod的详解(一)

关于K8s的Pod的详解&#xff08;一&#xff09; Pod和API server的通信加快Pod启动更改Pod的资源Pod 的持久卷的单个访问模式Pod 拓扑分布约束Pod 拓扑分布中的最小域数 Pod 作为k8s创建&#xff0c;调度&#xff0c;管理的基本单位。由上级的Controller对Node上安装的Kubelet发…

云安全攻防(二)之 云原生安全

云原生安全 什么是云原生安全&#xff1f;云原生安全包含两层含义&#xff1a;面向云原生环境的安全和具有云原生特征的安全 面向云原生环境的安全 面向云原生环境的安全的目标是防护云原生环境中的基础设施、编排系统和微服务系统的安全。这类安全机制不一定会具有云原生的…

基于SaaS模式的Java基层卫生健康云HIS系统源码【运维管理+运营管理+综合监管】

云HIS综合管理平台 一、模板管理 模板分为两种&#xff1a;病历模板和报表模板。模板管理是运营管理的核心组成部分&#xff0c;是基层卫生健康云中各医疗机构定制电子病历和报表的地方&#xff0c;各医疗机构可根据自身特点特色定制电子病历和报表&#xff0c;制作的电子病历…

Web-1-网站工作流程介绍

我们学习web开发&#xff0c;首先要知道什么是Web&#xff1f; Web: 全球广域网&#xff0c;也称为万维网(www World Wide Web)&#xff0c;能够通过浏览器访问的网站 比如我展示的这京东&#xff0c;淘宝唯品会都叫做网站&#xff0c;那么现在大家想一下&#xff0c;你还知道什…

论文笔记:Adjusting for Autocorrelated Errors in Neural Networks for Time Series

2021 NIPS 原来的时间序列预测任务是根据预测论文提出用一阶自回归误差预测 一阶差分&#xff0c;类似于ResNet的残差思路&#xff1f;记为pred&#xff0c;最终的预测结果

解决PicGo上传图片失败错误信息和上传图片失败包404错误以及Typora怎么一键导入本地图片到PicGo

&#x1f600;前言 解决PicGo上传图片失败错误信息和上传图片失败包404错误以及Typora怎么一键导入本地图片到PicGo &#x1f3e0;个人主页&#xff1a;尘觉主页 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是尘觉&#xff0c;希望我的文章可以帮助到大家&#x…

beego通过gorm访问mysql数据库

一、下载golang 二、解压下载包到C盘 三、配置golang系统环境变量 四、进入新建的工作目录C:\project下载并安装beego 五、将新生成的bee.exe所在的路径c:\project\bin加入到系统变量path里面 六、下载并安装mysql 例如在上图中&#xff0c; 选“No thanks,just start my down…

【unity之IMGUI实践】单例模式管理数据存储【二】

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;uni…

自动化运维工具—Ansible概述

Ansible是什么&#xff1f; Ansible是一个基于Python开发的配置管理和应用部署工具&#xff0c;现在也在自动化管理领域大放异彩。它融合了众多老牌运维工具的优点&#xff0c;Pubbet和Saltstack能实现的功能&#xff0c;Ansible基本上都可以实现。 Ansible能批量配置、部署、…

无涯教程-jQuery - Dropable移动函数

Drop-able 功能可与JqueryUI中的交互一起使用。此功能可在任何DOM元素上启用可放置功能。 Drop able - 语法 $( "#droppable" ).droppable(); Drop able - 示例 以下是一个简单的示例&#xff0c;显示了drop-able的用法- <html><head><title>…

爬虫小白-如何调试列表页链接与详情链接不一样并三种方式js逆向解决AES-ECB

目录 一、网站分析二、定位监听三、熟悉AES-ECB四、调试分析五、node运行js六、Python执行js 一、网站分析 三年前的案例&#xff0c;我的原始文章网站 &#xff0c;如图我们直接点击标题进入到详情页&#xff0c;链接会发生跳转&#xff0c;且与我们在详情看到的链接&#xf…

左神算法之中级提升班(8)

目录 【案例1】 【题目描述】 【思路解析】 【代码实现】 【案例2】 【题目描述】 【思路解析】 【代码实现】 【案例3】 【题目描述】 【思路解析】 【案例4】 【题目描述】 【思路解析】 【代码实现】 【案例5】 【题目描述】 【子序列概念】 【思路解析1 经典…

[NLP]LLaMA与LLamMA2解读

摘要 Meta最近提出了LLaMA(开放和高效的基础语言模型)模型参数包括从7B到65B等多个版本。最值得注意的是&#xff0c;LLaMA-13B的性能优于GPT-3&#xff0c;而体积却小了10倍以上&#xff0c;LLaMA-65B与Chinchilla-70B和PaLM-540B具有竞争性。 一、引言 一般而言&#xff0…

020 - STM32学习笔记 - Fatfs文件系统(二) - 移植与测试

020 - STM32学习笔记 - Fatfs文件系统&#xff08;二&#xff09; - 移植与测试 上节学习了FatFs文件系统的相关知识&#xff0c;这节内容继续学习在STM32上如何移植FatFs文件系统&#xff0c;并且实现文件的创建、读、写与删除等功能。各位看官觉得还行的话点点赞&#xff0c…

Python时间处理:探索time模块

日常工作中&#xff0c;经常涉及到一些时间的转换操作&#xff0c;比如某些业务针对时间的操作要转成不同的时区&#xff0c;有的要转换格式入库&#xff0c;有的需要跟时间对比等等&#xff0c;接下来我们一起来看一下python里面是怎么去处理时间的。 time模块简单介绍 Python…

几百本常用计算机开发语言电子书链接

GitHub - XiangLinPro/IT_book: 本项目收藏这些年来看过或者听过的一些不错的常用的上千本书籍&#xff0c;没准你想找的书就在这里呢&#xff0c;包含了互联网行业大多数书籍和面试经验题目等等。有人工智能系列&#xff08;常用深度学习框架TensorFlow、pytorch、keras。NLP、…

[STL]详解list模拟实现

[STL]list模拟实现 文章目录 [STL]list模拟实现1. 整体结构总览2. 成员变量解析3. 默认成员函数构造函数1迭代器区间构造函数拷贝构造函数赋值运算符重载析构函数 4. 迭代器及相关函数迭代器整体结构总览迭代器的模拟实现begin函数和end函数begin函数和end函数const版本 5. 数据…

心法利器[93] | 谈校招:技术面

心法利器 本栏目主要和大家一起讨论近期自己学习的心得和体会&#xff0c;与大家一起成长。具体介绍&#xff1a;仓颉专项&#xff1a;飞机大炮我都会&#xff0c;利器心法我还有。 2022年新一版的文章合集已经发布&#xff0c;累计已经60w字了&#xff0c;获取方式看这里&…