【C++】用红黑树封装map和set
我们之前学的map和set在stl源码中都是用红黑树封装实现的,当然,我们也可以模拟来实现一下。在实现之前,我们也可以看一下stl源码是如何实现的。我们上篇博客写的红黑树里面只是一个pair对象,这对于set来说显然是不合适的,所以要想让一个红黑树的代码同时支持map和set,就用上模板就可以了
我们来看看stl源码中是如何实现的
前两个模板参数是两个类型,就是我们要在set或map中放入什么
set不是只需要放入一个吗?所以,set在传参数的时候是这么传的
它的前两个传的全是Key,它这么实现还是为了兼容map,map传的是什么呢?我们再来看一下
传的一个是Key,一个是pair类的对象。那pair中不是已经有Key了吗,为什么还要传Key呢?因为一个最简单的原因之一find函数的参数是Key。
那么看第三个模板参数keyofvalue,传这个类型是为了从value中找到key,因为我们树这个类传给节点类的时候只传了value,如下图:
因为map中value是一个pair对象,set中value就是key,它们的获取方式不一样,所以传这个参数是为了实现仿函数,来取出key值用于比较。
那么了解了这个大体的结构之后,下一个就是要实现我们的迭代器了,我们其实可以在红黑树中实现一个树形的迭代器,然后map和set再封装一层就行了,其实我们的迭代器就是一个类,它用来实现类似于指针的一些操作,所以我们就用指针来当作这个类的成员变量,在这个类的基础上实现迭代器的功能。
在实现迭代器的时候,最关键的一个函数就是重载++,这里迭代器++肯定是按中序,因为这样才有意义,有顺序,那么我们如何通过一个节点找到它的中序遍历的下一个节点呢?这其实是有规律的。比如我们看这样一颗红黑树
首先我们中序遍历是左子树 根 右子树
1.假设这个节点有右子树,那么这个节点之后就是它的右子树的中序的第一个节点,就是右子树中最左边的节点
2.假设这个节点没有右子树,那么走完这个节点以后以这个节点为根的树就走完了,假如它是它父亲的左孩子,那么就该走它的父亲,如果它是它父亲的右孩子,那么它父亲也走完了,就按照此规律走它的爷爷。
有了这个理论基础,我们就可以来实现了。
同样--的话跟++是完全相反的,反过来的遍历顺序就是右子树,根,左子树,然后我们再分别去看这棵树有没有左子树,如果有,那就走左子树中第一个该走的节点,就是左子树中最右节点;如果没有,那就看它是它父亲的什么节点,一直往上找,直到找到它是它父亲的右子树的节点,它父亲就是下一个要遍历的节点。
下面还有一些细节问题,比如说把迭代器写成模板
那么只需要传不同的类型就可以实现const或非const的迭代器
我们const对象要用const版本的迭代器,因为const对象用普通版本的属于权限放大,所以我们要设计const版本的迭代器
我们也要对红黑树的插入函数进行修改,原来插入函数返回一个bool值,但是库中应该是返回一个pair对象,其中first是个迭代器,second是个bool值表示是否新插入
看到这样的代码的时候,这个typename表示后面是一个类型名,因为static静态成员也可以指明类域然后去访问
另外,我们这里为什么传const K呢?因为就算是普通的迭代器我们也不希望key值改变,因为map的key值改了就不满足二叉搜索树了
这是如何使用const_iterator,首先s就是一个普通的map对象,就调用普通版本的begin()
调完之后它返回一个iterator,而我们用的const_iterator去接收的,所以要写个构造函数,用普通迭代器构造出const迭代器
那么下面我们再整体的来展示一下红黑树和map set之间的封装关系
这就是如何用红黑树封装出map和set,下面是所有的代码
RBTree.h
#include<iostream>
#include<assert.h>
using namespace std;enum col {RED,BLACK
};
template<class T>
struct RBTreeNode {RBTreeNode(const T& data):_left(nullptr),_right(nullptr),_parent(nullptr),_data(data),_col(RED){}RBTreeNode* _left = nullptr;RBTreeNode* _right = nullptr;RBTreeNode* _parent = nullptr;T _data;col _col=RED;
};
template<class T,class Ptr,class Ref>
struct RBTreeIterator {typedef RBTreeNode<T> Node;typedef RBTreeIterator<T,Ptr,Ref> self;typedef RBTreeIterator<T, T*, T&> iterator;typedef RBTreeIterator<T, const T*, const T&> const_iterator;Node* _node;RBTreeIterator(const iterator& it):_node(it._node) {}RBTreeIterator(Node*node):_node(node){}Ref operator*() {return _node->_data;}Ptr operator->() {return &_node->_data;}bool operator==(const self&s) {return _node == s._node;}bool operator!=(const self& s) {return _node != s._node;}self& operator++() {if (_node == nullptr) {cout << "end()不能++" << endl;assert(false);}if (_node->_right) {//有右子树,那么这个节点之后就是它的右子树的中序的第一个节点,就是右子树中最左边的节点_node = _node->_right;while (_node->_left != nullptr)_node = _node->_left;return *this;}else {//没有右子树,直到找到孩子是父亲左子树的那个父亲节点Node* parent = _node->_parent;while (parent && _node != parent->_left) {parent = parent->_parent;_node = _node->_parent;}_node = parent;return *this;}}self& operator--() {if (_node->_left) {_node = _node->_left;while (_node->_right != nullptr)_node = _node->_right;return *this;}else {Node* parent = _node->_parent;while (parent && _node != parent->_right) {parent = parent->_parent;_node = _node->_parent;}_node = parent;return *this;}}
};template<class K,class V,class Keyofvalue>
class RBTree {typedef RBTreeNode<V> Node;
public:typedef RBTreeIterator<V,V*,V&> iterator;typedef RBTreeIterator<V,const V*,const V&> const_iterator;const_iterator begin()const {Node* cur = _root;while (cur && cur->_left)cur = cur->_left;return const_iterator(cur);}iterator begin() {Node* cur = _root;while (cur&&cur->_left)cur = cur->_left;return iterator(cur);}const_iterator end()const {return const_iterator(nullptr);}iterator end() {return iterator(nullptr);}iterator Find(const K& key) {Keyofvalue kov;Node* cur = _root;while (cur) {if (kov(cur->_data) < key) {cur = cur->_right;}else if (kov(cur->_data) > key) {cur = cur->_left;}else {return iterator(cur);}}return end();}pair<iterator,bool> insert(const V& data) {if (_root == nullptr) {_root = new Node(data);_root->_col = BLACK;return make_pair(iterator(_root),true);}Node* cur = _root;Node* parent = nullptr;Keyofvalue kov;while (cur) {if (kov(cur->_data) < kov(data)) {parent = cur;cur = cur->_right;}else if (kov(cur->_data) > kov(data)) {parent = cur;cur = cur->_left;}else return make_pair(iterator(cur),false);}cur = new Node(data);Node* ret = cur;if (kov(parent->_data) < kov(cur->_data)) {parent->_right = cur;cur->_parent = parent;}else {parent->_left = cur;cur->_parent = parent;}Node* c = cur;Node* p = cur->_parent;Node* g = p->_parent;Node* u = nullptr;while (p && p->_col == RED) {if (p == g->_left)u = g->_right;else u = g->_left;if (u == nullptr || u->_col == BLACK) {if (p == g->_left && c == p->_left) {RotateR(g);p->_col = BLACK;g->_col = RED;}else if (p == g->_right && c == p->_right) {RotateL(g);p->_col = BLACK;g->_col = RED;}else if (p == g->_left && c == p->_right) {RotateL(p);RotateR(g);c->_col = BLACK;g->_col = RED;}else if (p == g->_right && c == p->_left) {RotateR(p);RotateL(g);c->_col = BLACK;g->_col = RED;}else assert(false);break;}else if (u->_col == RED) {p->_col = BLACK;u->_col = BLACK;g->_col = RED;if (g == _root) {g->_col = BLACK;break;}else {c = g;p = c->_parent;g = p->_parent;}}else assert(false);}return make_pair(iterator(ret),true);}void RotateL(Node* parent) {Node* subR = parent->_right;Node* subRL = subR->_left;Node* ppnode = parent->_parent;if (subRL)subRL->_parent = parent;parent->_right = subRL;subR->_left = parent;parent->_parent = subR;if (parent == _root) {_root = subR;subR->_parent = nullptr;}else {subR->_parent = ppnode;if (ppnode->_left == parent)ppnode->_left = subR;else ppnode->_right = subR;}}void RotateR(Node* parent) {Node* subL = parent->_left;Node* subLR = subL->_right;Node* ppnode = parent->_parent;if (subLR)subLR->_parent = parent;parent->_left = subLR;subL->_right = parent;parent->_parent = subL;if (parent == _root) {_root = subL;subL->_parent = nullptr;}else {subL->_parent = ppnode;if (ppnode->_left == parent)ppnode->_left = subL;else ppnode->_right = subL;}}Node* getroot() {return _root;}private:Node* _root = nullptr;
};
MySet.h
namespace jxh {template<class T>class set {typedef RBTreeNode<T> Node;struct keyofvalue {const T& operator()(const T&key) {return key;}};void _inorder(Node* root) {if (root == nullptr)return;_inorder(root->_left);cout << root->_data << endl;_inorder(root->_right);}public:typedef typename RBTree<T, const T, keyofvalue>::iterator iterator;typedef typename RBTree<T, const T, 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 T& key){return _t.insert(key);}iterator find(const T& key){return _t.find(key);}void inorder() {_inorder(_t.getroot());}private:RBTree<T,const T,keyofvalue> _t;};
MyMap.h
namespace jxh {template<class K,class V>class map {typedef RBTreeNode<pair<K,V>> Node;struct keyofvalue {const K& operator()(const pair<K,V>& kv) {return kv.first;}};void _inorder(Node* root) {if (root == nullptr)return;_inorder(root->_left);cout << root->_data.first<<" "<<root->_data.second << endl;_inorder(root->_right);}public://typedef RBTreeIterator<pair<K,V>> iterator;typedef typename RBTree<K, pair<const K, V>, keyofvalue>::iterator iterator;typedef typename RBTree<K, pair<const K, V>, keyofvalue>::const_iterator const_iterator;const_iterator begin()const {return _t.begin();}const_iterator end() const{return _t.end();}iterator begin() {return _t.begin();}iterator end() {return _t.end();}pair<iterator, bool> insert(const pair<K, V>& kv){return _t.insert(kv);}iterator find(const K& key){return _t.find(key);}V& operator[](const K& key){pair<iterator, bool> ret = insert(make_pair(key, V()));return ret.first->second;}void inorder() {_inorder(_t.getroot());}private:RBTree<K, pair<const K,V>, keyofvalue> _t;};
相关文章:
【C++】用红黑树封装map和set
我们之前学的map和set在stl源码中都是用红黑树封装实现的,当然,我们也可以模拟来实现一下。在实现之前,我们也可以看一下stl源码是如何实现的。我们上篇博客写的红黑树里面只是一个pair对象,这对于set来说显然是不合适的ÿ…...
一些好玩的东西
这里写目录标题 递归1.递归打印数组和链表?代码实现原理讲解二叉树的 前 中 后 序位置 参考文章 递归 1.递归打印数组和链表? 平常我们打印数组和链表都是 迭代 就好了今天学到一个新思路–>不仅可以轻松正着打印数组和链表 , 还能轻松倒着打印(用的是二叉树的前中后序遍…...
微电网优化:基于巨型犰狳优化算法(Giant Armadillo Optimization,GAO)的微电网优化(提供MATLAB代码)
一、微电网优化模型 微电网是一个相对独立的本地化电力单元,用户现场的分布式发电可以支持用电需求。为此,您的微电网将接入、监控、预测和控制您本地的分布式能源系统,同时强化供电系统的弹性,保障您的用电更经济。您可以在连接…...
java锁
乐观锁 乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,采取在写时先读出…...
QA测试开发工程师面试题满分问答6: 如何判断接口功能正常?从QA的角度设计测试用例
判断接口功能是否正常的方法之一是设计并执行相关的测试用例。下面是从测试QA的角度设计接口测试用例的一些建议,包括功能、边界、异常、链路、上下游和并发等方面: 通过综合考虑这些测试维度,并设计相应的测试用例,可以更全面地评估接口的功能、性能、安全性、数据一致…...
vue 双向绑定
双向绑定:双方其中一方改变,另外一方也会跟着改变。 data() { return {inputValue: ,list: [],message: hello,checked: true,radio: ,select: [],options: [{text: A, value:{value: A}},{text: B, value:{value: B}},{text: C, value:{value: C}}], }…...
python--异常处理
异常处理 例一: try: #可能出现异常代码 except: #如果程序异常,则立刻进入这儿 [finally: #不管是否捕获异常,finally语法快必须要执行!!! #资源关闭,等各种非常重要的操作&…...
element-ui result 组件源码分享
今日简单分享 result 组件的源码实现,主要从以下三个方面: 1、result 组件页面结构 2、result 组件属性 3、result 组件 slot 一、result 组件页面结构 二、result 组件属性 2.1 title 属性,标题,类型 string,无默…...
VRRP虚拟路由实验(思科)
一,技术简介 VRRP(Virtual Router Redundancy Protocol)是一种网络协议,用于实现路由器冗余,提高网络可靠性和容错能力。VRRP允许多台路由器共享一个虚拟IP地址,其中一台路由器被选为Master,负…...
SpringBoot通用模块--文件上传开发(阿里云OSS)
文件上传,是指将本地图片、视频、音频等文件上传到服务器上,可以供其他用户浏览或下载的过程。文件上传在项目中应用非常广泛,我们经常发抖音、发朋友圈都用到了文件上传功能。 实现文件上传服务,需要有存储的支持,那…...
Fecify 商品标签功能
关于商品标签 商品标签是指商家可以在展示商品时,自己创建一个自定义标签,可自定义某个关键词或短语。这样顾客在浏览商城时,只需要通过标签就能看到更直观的展示信息。 商品标签可以按照用户的属性、行为、偏好等进行分类,标签要…...
openstack中windows虚拟机时间显示异常问题处理
文章目录 一、问题描述二、元数据信息总结 一、问题描述 openstack创建出windows虚拟机的时候,发现时间和当前时间相差8小时,用起来很难受。 参考:https://www.cnblogs.com/hraa0101/p/11365238.html 二、元数据信息 通过设置镜像的元数据…...
很牛的一套仓库管理系统,免费复用【带源码】
今天给大家分享一套基于SpringbootVue的仓库管理系统源码,在实际项目中可以直接复用。(免费提供,文末自取) 一、系统运行图(设计报告和接口文档) 1、登陆页面 2、物品信息管理 3、设计报告包含接口文档 二、系统搭建视频教程 …...
Spark 部署与应用程序交互简单使用说明
文章目录 前言步骤一:下载安装包Spark的目录和文件 步骤二:使用Scala或PySpark Shell本地 shell 运行 步骤3:理解Spark应用中的概念Spark Application and SparkSessionSpark JobsSpark StagesSpark Tasks 转换、立即执行操作和延迟求值窄变换和宽变换 S…...
【二分查找】Leetcode 点名
题目解析 LCR 173. 点名 算法讲解 1. 哈希表 class Solution { public:int takeAttendance(vector<int>& nums) {map<int, int> Hash;for(auto n : nums) Hash[n];for(int i 0; i < nums[nums.size() - 1]; i){if(Hash[i] 0)return i;}return nums.si…...
JS中的运算符
1.&& 逻辑与 &&会从左到右执行表达式,直到某个表达式的运行结果返回false,如果全部为true,则返回最后一个中表达式的执行结果 console.log(1 && 2) // 2 console.log(1&&10&&15) // 15 console.log(1&&0&&am…...
Webots常用的执行器(Python版)
文章目录 1. RotationalMotor2. LinearMotor3. Brake4. Propeller5. Pen6. LED 1. RotationalMotor # -*- coding: utf-8 -*- """motor_controller controller."""from controller import Robot# 实例化机器人 robot Robot()# 获取基本仿真步长…...
mySql数据库学习002-表数据查询操作
表数据查询操作 表数据如下: idnameagegenderclasscreatedAtupdatedAt1张三20男一班2024-04-08 09:15:092024-04-08 09:15:092李四19女一班2024-04-08 09:15:092024-04-08 09:15:093王五21女二班2024-04-08 09:15:092024-04-08 09:15:094赵六18女二班2024-04-08 0…...
【STL】stack与queue的底层原理及其实现
文章目录 stack的介绍库中stack的使用栈的模拟实现queue的介绍库中queue的使用queue的模拟实现 stack的介绍 (图片来自知乎) 1.stack是一种容器适配器,模拟了栈的数据结构。数据只能从一端进去,另一端出来(先进后出&am…...
Ai大模型如何应用到机器视觉系统中
AI大模型在机器视觉系统中的应用可以通过以下几个步骤实现: 1. 数据准备与预处理: - 收集和标注大量高质量的图像数据,这些数据应该覆盖机器视觉系统需要处理的各种场景和对象。 - 对图像数据进行预处理,包括去噪、标准化、增强等…...
Docker 离线安装指南
参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性,不同版本的Docker对内核版本有不同要求。例如,Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本,Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...
Docker 运行 Kafka 带 SASL 认证教程
Docker 运行 Kafka 带 SASL 认证教程 Docker 运行 Kafka 带 SASL 认证教程一、说明二、环境准备三、编写 Docker Compose 和 jaas文件docker-compose.yml代码说明:server_jaas.conf 四、启动服务五、验证服务六、连接kafka服务七、总结 Docker 运行 Kafka 带 SASL 认…...
微服务商城-商品微服务
数据表 CREATE TABLE product (id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 商品id,cateid smallint(6) UNSIGNED NOT NULL DEFAULT 0 COMMENT 类别Id,name varchar(100) NOT NULL DEFAULT COMMENT 商品名称,subtitle varchar(200) NOT NULL DEFAULT COMMENT 商…...
Python如何给视频添加音频和字幕
在Python中,给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加,包括必要的代码示例和详细解释。 环境准备 在开始之前,需要安装以下Python库:…...
UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)
UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中,UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化…...
【JavaSE】多线程基础学习笔记
多线程基础 -线程相关概念 程序(Program) 是为完成特定任务、用某种语言编写的一组指令的集合简单的说:就是我们写的代码 进程 进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存…...
【JVM】Java虚拟机(二)——垃圾回收
目录 一、如何判断对象可以回收 (一)引用计数法 (二)可达性分析算法 二、垃圾回收算法 (一)标记清除 (二)标记整理 (三)复制 (四ÿ…...
使用SSE解决获取状态不一致问题
使用SSE解决获取状态不一致问题 1. 问题描述2. SSE介绍2.1 SSE 的工作原理2.2 SSE 的事件格式规范2.3 SSE与其他技术对比2.4 SSE 的优缺点 3. 实战代码 1. 问题描述 目前做的一个功能是上传多个文件,这个上传文件是整体功能的一部分,文件在上传的过程中…...
用递归算法解锁「子集」问题 —— LeetCode 78题解析
文章目录 一、题目介绍二、递归思路详解:从决策树开始理解三、解法一:二叉决策树 DFS四、解法二:组合式回溯写法(推荐)五、解法对比 递归算法是编程中一种非常强大且常见的思想,它能够优雅地解决很多复杂的…...
用鸿蒙HarmonyOS5实现国际象棋小游戏的过程
下面是一个基于鸿蒙OS (HarmonyOS) 的国际象棋小游戏的完整实现代码,使用Java语言和鸿蒙的Ability框架。 1. 项目结构 /src/main/java/com/example/chess/├── MainAbilitySlice.java // 主界面逻辑├── ChessView.java // 游戏视图和逻辑├── …...
















