【map、set】C++用红黑树来封装map、set容器

图片名称
🎉博主首页: 有趣的中国人

🎉专栏首页: C++进阶

🎉其它专栏: C++初阶 | Linux | 初阶数据结构

在这里插入图片描述

小伙伴们大家好,本片文章将会讲解map和set用红黑树来封装map、set容器的相关内容。


如果看到最后您觉得这篇文章写得不错,有所收获,麻烦点赞👍、收藏🌟、留下评论📝。您的支持是我最大的动力,让我们一起努力,共同成长!

文章目录

  • `0. 前言`
  • `1. map、set底层源码一览`
    • ==<font color = blue size = 4><b>⌛底层查看⏳==
  • `2. 改造红黑树`
    • ==<font color = blue size = 4><b>⏳map类和set类⌛==
    • ==<font color = blue size = 4><b>⏳仿函数,这思想很重要⌛==
    • ==<font color = blue size = 4><b>⏳第一个K的作用⌛==
  • `3. 迭代器的模拟实现`
    • ==<font color = blue size = 4><b>⏳迭代器++⌛==
    • ==<font color = blue size = 4><b>⏳迭代器- -⌛==
  • `4. map的operator[]`
  • `5. 拷贝构造、析构、赋值重载`
    • ==<font color = blue size = 4><b>⏳析构函数⌛==
    • ==<font color = blue size = 4><b>⏳拷贝构造⌛==
    • ==<font color = blue size = 4><b>⏳赋值重载⌛==



0. 前言


首先借用某位大佬的一句话:学习制造轮子不是为了打造更出色的轮子,而是为了汲取大师们卓越的思想,以此启迪我们的认知与创造力。



1. map、set底层源码一览


我们在模拟红黑树的时候一律用了pairKV模型来进行实现。但是由于mapKV模型的而setK型的,但是底层都是用的红黑树,那么应该如何进行调整呢?

⌛底层查看⏳

在这里插入图片描述

可以看到,对于K类型的set,模板传入了两个相同的K类型给RBTree;对于KV类型的map,模板传入了一个K,还有一个pair<K, V>类型给RBTree。

在RBTree类中将第二个模板参数传入给RBTreeNode,data的数据类型为就是此类型,这样就解决了此问题。



2. 改造红黑树


⏳map类和set类⌛


我们先按照底层的思路实现以下map类和set类:

// mymap.h
#pragma once
#include "RBTtree.h"namespace dsj
{template<class K, class V>class map{public:private:// pair<K, V> 为节点中的参数类型RBTree<K, pair<K, V>> rt;};
}// myset.h
#pragma once
#include "RBTtree.h"namespace dsj
{template<class K>class set{public:private:// 第二个 K 为节点中的参数类型RBTree<K, K> rt;};
}

💻RBTreeNode的修改
在这里插入图片描述
💻RBTree的修改
在这里插入图片描述

这样一改,问题就来了,在我们插入数据的时候,如何根据插入节点的K值来判断他插入所在的位置呢?如何进行比较?

可以用仿函数来进行比较。


⏳仿函数,这思想很重要⌛


虽然在RBTree中并不知道如何进行比较,但是在mapset类中知道如何进行比较,即mappairfirst进行比较,set直接用K值进行比较,实现方法如下:

// mymap.h
namespace dsj
{// 仿函数template <class K, class V>struct mapKeyOfT{// 取pair的firstconst K& operator()(const pair<K, V>& kv){return kv.first;}};template<class K, class V>class map{public:private:// 多加一个仿函数模板参数,对于map,取pair的first进行比较RBTree<K, pair<K, V>, mapKeyOfT<K, V>> rt;};
}// myset.h
namespace dsj
{// 仿函数template <class K>struct setKeyOfT{// 取出keyconst K& operator()(const K& key){return key;}};template<class K>class set{public:private:// 多加一个仿函数模板参数,对于set,直接对K值进行比较RBTree<K, K, setKeyOfT<K>> rt;};
}

在这里插入图片描述

⏳第一个K的作用⌛


有一个问题,给红黑树传模板参数的时候,第一个参数K类型的作用是什么呢?

1. 对于insert来说,setmap都可以不要第一个模板参数K,因为set中第二个模板参数是K,可以用来进行插入时的比较,对于map,也是用第二个模板参数来进行比较;

2. 但是对于find接口,他就需要K



3. 迭代器的模拟实现


通过list的迭代器,我们知道如果原生指针的效果达不到我们想要的效果,就通过封装迭代器,再用运算符重载来达到我们预期的效果。


模拟实现:
template<class T>
struct __RBTIterator
{typedef RBTNode<T> Node;typedef __RBTIterator<T> Self;Node* _node;__RBTIterator(Node* node):_node(node){}T operator*(){return _node->_data;}T* operator->(){return &_node->_data;}bool operator!=(const Self& s){return _node != s._node;}
};

我们在模拟实现list的迭代器的时候讲过,对于const迭代器和普通迭代器,他们只有解引用->的的返回值不一样,其他都相同,如果重新写一遍那么代码肯定非常冗余,因此我们这里还用类似的方法来实现(传入模板参数)。

改造后:

template<class T, class Ref, class Ptr>
struct __RBTIterator
{typedef RBTNode<T> Node;typedef __RBTIterator<T, Ref, Ptr> Self;Node* _node;__RBTIterator(Node* node):_node(node){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}bool operator!=(const Self& s){return _node != s._node;}
};

RBTree中:

// 编译器自动推演,根据类型调用对应的迭代器
typedef __RBTIterator<T, T&, T*> Iterator;
typedef __RBTIterator<T, const T&, const T*> Const_Iterator;Iterator begin()
{Node* leftMin = _root;// 加上leftMin是为了预防_root为空的情况if (leftMin && leftMin->_left){leftMin = leftMin->_left;}return Iterator(leftMin);
}Iterator End()
{return Iterator(nullptr);
}Const_Iterator begin() const
{Node* leftMin = _root;if (leftMin && leftMin->_left){leftMin = leftMin->_left;}return Const_Iterator(leftMin);
}Const_Iterator End() const
{return Const_Iterator(nullptr);
}

map和set中:

template<class K, class V>
class map
{
public:typedef typename RBTree<K, pair<K, V>, mapKeyOfT<K, V>>::Iterator iterator;typedef typename RBTree<K, pair<K, V>, mapKeyOfT<K, V>>::Const_Iterator const_iterator;const_iterator begin() const{return rt.Begin();}const_iterator end() const{return rt.End();}iterator begin(){return rt.Begin();}iterator end(){return rt.End();}private:RBTree<K, pair<K, V>, mapKeyOfT<K, V>> rt;
};template<class K>
class set
{
public:typedef typename RBTree<K, K, setKeyOfT<K>>::Iterator iterator;typedef typename RBTree<K, K, setKeyOfT<K>>::Const_Iterator const_iterator;const_iterator beign() const{return rt.Begin();}const_iterator end() const{return rt.End();}iterator beign(){return rt.Begin();}iterator end(){return rt.End();}
private:RBTree<K, K, setKeyOfT<K>> rt;
};

调用逻辑:
在这里插入图片描述

⏳迭代器++⌛


set和map迭代器的++按照中序遍历的顺序进行加加的,顺序为:左子树 根 右子树。

逻辑如下:

  1. 右子树存在,下一个访问节点是右子树的最左节点;
  2. 右子树不存在,向上找,找到孩子是父亲左节点的那个祖先节点。

代码:

Self& operator++()
{Node* cur = _node;if (cur->_right){Node* rightMin = cur->_right;while (rightMin->_left){rightMin = rightMin->_left;}_node = rightMin;}else{Node* parent = cur->_parent;// 这里加parent是为了预防访问最右节点后parent为空的情况while (parent && cur == parent->_right){cur = parent;parent = cur->_parent;}_node = parent;}return *this;
}

⏳迭代器- -⌛

思路同上,但是顺序相反:右子树 根 左子树。

逻辑如下:

  1. 左子树存在,下一个访问节点是左子树的最右节点;
  2. 左子树不存在,向上找,找到孩子是父亲右节点的那个祖先节点。

代码:

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


4. map的operator[]


mapoperator[]的用法在之前的文章中讲过,它的作用是根据key的值进行查找,如果存在,那么就返回当前节点的value,如果不存在,就先插入此节点,然后再返回这个节点的value

实现代码:

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

Insert函数修改逻辑:

Insert函数,我们要改变它的返回值,返回值是pair

  • 如果插入成功,返回插入节点所在的迭代器;
  • 如果插入失败,返回K值相等的节点的迭代器。
  • 如果要进行旋转,要先记录一下当前cur的节点,因为旋转后cur的节点可能就不是我们新插入的节点了。

在这里插入图片描述



5. 拷贝构造、析构、赋值重载


⏳析构函数⌛


析构函数要用后序,这样更为简单:

~RBTree()
{Destroy(_root);
}
void Destroy(Node* root)
{// 左右根的顺序进行析构if (root == nullptr){return;}Destroy(root->_left);Destroy(root->_right);delete root;
}

⏳拷贝构造⌛


拷贝构造要用先序:

RBTree(const RBTree<K, T, KeyOfT>& t)
{_root = Copy(t._root);
}Node* Copy(Node* root)
{if (root == nullptr)return nullptr;Node* newroot = new Node(root->_data);newroot->_col = root->_col;newroot->_left = Copy(root->_left);if (newroot->_left)newroot->_left->_parent = newroot;newroot->_right = Copy(root->_right);if (newroot->_right)newroot->_right->_parent = newroot;return newroot;
}

⏳赋值重载⌛


下面是现代写法,之前讲过:

const RBTree<K, T, KeyOfT>& operator=(const RBTree<K, T, KeyOfT> t)
{swap(_root, t._root);return *this;
}

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

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

相关文章

CasaOS系统玩客云安装内网穿透工具实现无公网IP远程访问

文章目录 前言1. CasaOS系统介绍2. 内网穿透安装3. 创建远程连接公网地址4. 创建固定公网地址远程访问 前言 2月底&#xff0c;玩客云APP正式停止运营&#xff0c;不再提供上传、云添加功能。3月初&#xff0c;有用户进行了测试&#xff0c;局域网内的各种服务还能继续使用&am…

kubeadm部署k8s v1.28

一、主机准备 主机硬件配置说明 作用IP地址操作系统配置k8s-master01192.168.136.55openEuler-22.03-LTS-SP12颗CPU 4G内存 50G硬盘k8s-node01192.168.136.56openEuler-22.03-LTS-SP12颗CPU 4G内存 50G硬盘k8s-node02192.168.136.57openEuler-22.03-LTS-SP12颗CPU 4G内存 50G…

C++: 多态

目录 一、多态的概念 二、多态的定义及实现 2.1虚函数 2.2虚函数的重写 2.3多态的构成条件 2.4虚函数重写的两个例外 1.协变 2.析构函数的重写 2.5虚函数重写的实质 2.6override 和 final&#xff08;C11&#xff09; 1.final 2.override 2.7重载、覆盖&#xff0…

Leetcode刷题笔记2:数组基础2

导语 leetcode刷题笔记记录&#xff0c;本篇博客记录数组基础1部分的题目&#xff0c;主要题目包括&#xff1a; 977.有序数组的平方 &#xff0c;209.长度最小的子数组 &#xff0c;59.螺旋矩阵II 知识点 滑动窗口 所谓滑动窗口&#xff0c;就是不断的调节子序列的起始位…

反射获取或修改对象属性的值

利用反射既可以获取也可以写入,首先咱们先写几个获取的例子。 一:利用反射修改各数据(利用resultField.set修改) 首先定义实体类 public class Dog {private String dogUser;private int age;把DogUser的"hahaha"改为"geggegegege" Dog dog = new Do…

Docker快速搭建Oracle服务

服务器&#xff1a;CentOS7.9 1.安装docker yum install -y docker 2. 设置镜像加速 修改 /etc/docker/daemon.json 文件并添加上 registry-mirrors 键值 阿里云的docker镜像需要自己注册账号&#xff0c;也可以不注册账号&#xff0c;直接使用下面的连接。 也可以写入多…

C语言 数组——计算最大值的函数实现

目录 计算最大值 计算最大值的函数实现 应用实例&#xff1a;计算班级最高分​编辑​编辑 返回最大值所在的下标位置 返回最大值下标位置的函数实现​编辑 一个综合应用实例——青歌赛选手评分​编辑​编辑​编辑​编辑​编辑 计算最大值 计算最大值的函数实现 应用实例&…

键盘盲打是练出来的

键盘盲打是练出来的&#xff0c;那该如何练习呢&#xff1f;很简单&#xff0c;看着屏幕提示跟着练。屏幕上哪里有提示呢&#xff1f;请看我的截屏&#xff1a; 截屏下方有8个带字母的方块按钮&#xff0c;这个就是提示&#xff0c;也就是我们常说的8个基准键位&#xff0c;我…

【蓝桥杯】

题目列表 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) #include<bits/stdc.h> using llunsigned long long; #define int ll const int N2e510; int k0; std::string s; int a,b,c,d; void solve() {char op;std::cin>>op;if(opA){std::string s;for(int i1;i&l…

Kafka-偏移量(含消费者事务)

Kafka概述 1.什么是偏移量&#xff1a; 在 Kafka 中&#xff0c;每个分区的消息都会被分配一个唯一的偏移量&#xff08;offset&#xff09;。偏移量简单来说就是消息在分区中的位置标识。 偏移量从 0 开始递增&#xff0c;每条消息的偏移量都会比前一条消息的偏移量大 1。 消…

如何对Linode Windows虚拟机进行“本地”访问

大部分时候&#xff0c;IT运维工作都可以远程进行&#xff0c;只要能通过网络访问被管理的系统&#xff0c;就可以执行几乎所有任务。如果因为某些原因导致无法通过网络访问呢&#xff1f;此时可能需要亲自到达相关硬件设备旁&#xff0c;通过“本地访问”来排错。 如果这些硬…

python实现520表白图案

今天是520哦&#xff0c;作为程序员有必要通过自己的专业知识来向你的爱人表达下你的爱意。那么python中怎么实现绘制520表白图案呢&#xff1f;这里给出方法&#xff1a; 1、使用图形库&#xff08;如turtle&#xff09; 使用turtle模块&#xff0c;你可以绘制各种形状和图案…

Thinkphp内核开发盲盒商城源码v2.0 对接易支付/阿里云短信/七牛云存储

源码简介 这套系统是我从以前客户手里拿到的,100完整可用,今天测试防红链接失效了,需要修改防红API即可!前端页面展示我就不放了,懂的都懂 优点是Thinkphp开发的&#xff0c;二开容易。 源码图片 资源获取&#xff1a;Thinkphp内核开发盲盒商城源码v2.0 对接易支付/阿里云短…

苹果MacOS系统使用微软远程桌面连接Windows电脑桌面详细步骤

文章目录 前言1. 测试本地局域网内远程控制1.1 Windows打开远程桌面1.2 局域网远程控制windows 2. 测试Mac公网远程控制windows2.1 在windows电脑上安装cpolar2.2 Mac公网远程windows 3. 配置公网固定TCP地址 前言 日常工作生活中&#xff0c;有时候会涉及到不同设备不同操作系…

面了一个程序员,因为6休1拒绝了我

人一辈子赖以生存下去的主要就考虑三件事&#xff0c;职业&#xff0c;事业&#xff0c;副业&#xff0c;有其1-2都是很不错的。如果还没到40岁&#xff0c;那不妨提前想下自己可能遇到的一些情况&#xff0c;提前做一些准备&#xff0c;未雨绸缪些。 今年整体就业大环境也一般…

CDN用户平台安装说明

CDN用户平台安装说明 登录管理员系统 在”系统设置” – “高级设置” – “用户节点”中点击”添加节点” 如果所示&#xff1a; 节点名称 - 可以任意填写 进程监听端口 - 启动用户节点后&#xff0c;进程所监听的端口&#xff0c;通常是HTTP 80或者HTTPS 443&#xff0c;…

网关路由SpringCloudGateway、nacos配置管理(热更新、动态路由)

文章目录 前言一、网关路由二、SpringCloudGateway1. 路由过滤2. 网关登录校验2.1 鉴权2.2 网关过滤器2.3 登录校验2.3.1 JWT2.3.2 登录校验过滤器 3. 微服务从网关获取用户4. 微服务之间用户信息传递 三、nacos配置管理问题引入3.1 配置共享3.1.1 在Nacos中添加共享配置3.1.2 …

vue.js状态管理和服务端渲染

状态管理 vuejs状态管理的几种方式 组件内管理状态&#xff1a;通过data&#xff0c;computed等属性管理组件内部状态 父子组件通信&#xff1a;通过props和自定义事件实现父子组件状态的通信和传递 事件总线eventBus&#xff1a;通过new Vue()实例&#xff0c;实现跨组件通…

yolov8训练自己数据集时出现loss值为nan。

具体原因目前暂未寻找到。 解决办法 将参数amp改成False即可。 相关资料&#xff1a; https://zhuanlan.zhihu.com/p/165152789 https://github.com/ultralytics/ultralytics/issues/1148