【C++】:红黑树的应用 --- 封装map和set

点击跳转至文章:【C++】:红黑树深度剖析 — 手撕红黑树!

目录

  • 前言
  • 一,红黑树的改造
    • 1. 红黑树的主体框架
    • 2. 对红黑树节点结构的改造
    • 3. 红黑树的迭代器
      • 3.1 迭代器类
      • 3.2 Begin() 和 End()
  • 四,红黑树相关接口的改造
    • 4.1 Find 函数的改造
    • 4.2 Insert 函数的改造
  • 五,红黑树改造的完整代码
  • 六,set 的封装实现
  • 七,map 的封装实现

前言

map 和 set 的底层本质上还是复用通过对红黑树的改造,再分别套上一层 map 和 set 的 “壳子”,以达到 “一树二用” 的目的

在改造红黑树的过程中,我大概归纳了以下几个需要重点解决的问题:

(1) 对红黑树节点的改造。关联式容器中存储的是<key, value>的键值对,K为key的类型,如果是 set 则是 K,如果是map,则为pair<K, V>。如何用一个节点结构控制两种类型,使用类模板参数T

(2) 在插入操作时,如何完成数据的比较。由于我们的节点类型的泛型,如果是 set 则是 K,如果是map,则为pair<K, V>,而pair的比较是由 first 和 second 共同决定的,这显然不符合要求。因此插入数据时不能直接比较,要在 set 和 map 类中实现一个 KeyOfT 的仿函数,以便单独获取两个类型中的 key 数据

(3) 在红黑树中实现普通迭代器和const迭代器,再套上 “壳子”

(4) 关于 key 的修改问题。在STL库中,set 和 map 的 key 都是不能修改的,因为要符合二叉搜索树的特性,但是 map 中的 value 又是可以修改的。这个问题需要单独处理。

(5) 红黑树相关接口的改造。其中包括对 Find 和 Insert 函数的改造,特别是 Insert,因为在 map 里实现 operator[] 时需要依赖 Insert 函数。

说明:如果大家要自己动手实现封装,可以按照上面五个问题的流程进行实现。但是在本篇文章中由于展示等的原因,无法按照上面的步骤

一,红黑树的改造

1. 红黑树的主体框架

(1) K 是给find,erase用的,T 是给节点,insert用的

(2) KeyOfT 是由于下面需要比较,但是比较时不知道T的类型, set是key类型的比较,map是pair类型的比较,要统一变成key的比较

template <class K, class T, class KeyOfT>
class RBTree
{typedef RBTreeNode<T> Node; //节点
public:typedef RBTreeIterator<T, T&, T*> Iterator; //普通迭代器typedef RBTreeIterator<T, const T&, const T*> ConstIterator; //const 迭代器//其他功能的实现……private:Node* _root = nullptr;
};

2. 对红黑树节点结构的改造

//枚举颜色
enum Colour
{RED,BLACK
};//节点类
template <class T>
struct RBTreeNode
{T _data;RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;//pair<K, V> _kv;Colour _col;RBTreeNode(const T& data):_left(nullptr), _right(nullptr), _parent(nullptr), _data(data), _col(BLACK){}
};

3. 红黑树的迭代器

3.1 迭代器类

//迭代器类
template <class T, class Ref, class Ptr>
struct RBTreeIterator
{typedef RBTreeNode<T> Node;typedef RBTreeIterator<T, Ref, Ptr> Self;Node* _node;RBTreeIterator(Node* node):_node(node){}bool operator!=(const Self& s){return s._node != _node;}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}//从局部的某一个过程考虑Self& operator++(){if (_node->_right){//右不为空,右子树的最左节点就是下一个访问的节点Node* leftMost = _node->_right;while (leftMost->_left)leftMost = leftMost->_left;_node = leftMost;}else{//右为空,代表当前节点所在的子树已经访问完了,下一个访问的节点是祖先//沿着到根节点的那个路径查找,孩子是父亲左的那个祖先节点就是下一个访问的节点Node* cur = _node;Node* parent = cur->_parent;//循环找祖先while (parent && cur == parent->_right){cur = parent;parent = parent->_parent;}_node = parent;}return *this;}
};

迭代器中最复杂的就是 operator++()的实现,它与原先的 vector/list 不同,红黑树的迭代器是要完成二叉树的中序遍历

为了完成二叉树的中序遍历,我们需要从局部的某一过程考虑,就是假设 it 已经走到了某一节点,要找到下一个访问的节点,分为两种情况:

(1) 当前节点的右子树不为空

如下图,假设 it 已经到达了13节点,说明此时13的左子树已经访问完了,右子树不为空,下一个要访问的节点就是右子树的最左节点15

在这里插入图片描述

(2) 当前节点的右子树为空

如下图,假设 it 此时到达了15节点,它的右子树为空,下一个访问哪个节点呢?有些人单纯的认为是15的父亲17,其实是错误的

那假设 it 到达6节点上呢,6的右为空,但是根据中序遍历的顺序,6的父亲1已经访问过了。

在这里插入图片描述

其实此时是要找当前节点的祖先,父亲也是祖先之一

右为空,代表当前节点所在的子树已经访问完了,下一个访问的节点是祖先,是哪个祖先呢?沿着到根节点的那个路径查找,孩子是父亲左的那个祖先节点就是下一个访问的节点

(a) 假设此时走到了15节点,下一个访问的节点是17,cur 是 parent 的左,parent 就是下一个要访问的那个祖先节点;

在这里插入图片描述

(b) 假设此时走到了6节点,下一个访问的节点是8,但是此时 cur 是 parent 的右,不满足条件,继续向上查找,cur = parent,parent = parent->_parent,这时 cur 在1,parent 在8,cur 是 parent 的左,parent 就是下一个要访问的那个祖先节点

在这里插入图片描述

3.2 Begin() 和 End()

//中序遍历,找树的最左节点
Iterator Begin()
{//Node* cur = _root;Node* leftMost = _root;while (leftMost->_left)leftMost = leftMost->_left;return Iterator(leftMost);
}Iterator End()
{return Iterator(nullptr);
}ConstIterator Begin()const
{//Node* cur = _root;Node* leftMost = _root;while (leftMost->_left)leftMost = leftMost->_left;return ConstIterator(leftMost);
}ConstIterator End()const
{return ConstIterator(nullptr);
}Iterator Find(const K& key)
{Node* cur = _root;while (cur){if (cur->_data > key)cur = cur->_left;else if (cur->_data < key)cur = cur->_right;elsereturn Iterator(cur);}return End();
}

四,红黑树相关接口的改造

4.1 Find 函数的改造

查找成功,返回查找到的那个节点的迭代器,查找失败,就返回 nullptr。

Iterator Find(const K& key)
{Node* cur = _root;while (cur){if (cur->_data > key)cur = cur->_left;else if (cur->_data < key)cur = cur->_right;elsereturn Iterator(cur);}return End();
}

4.2 Insert 函数的改造

map 里的 operator[] 需要依赖 Insert 的返回值

pair<Iterator, bool> Insert(const T& data)
{if (_root == nullptr){_root = new Node(data);return make_pair(Iterator(_root), true);}KeyOfT kot;Node* cur = _root;Node* parent = nullptr;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;}elsereturn make_pair(Iterator(cur), false);}cur = new Node(data);Node* newnode = cur;//此处省略变色+旋转部分的代码……

五,红黑树改造的完整代码

说明:由于代码太长,影响展示效果,所以插入部分的 变色+旋转 的代码此处省略,和红黑树的基本一模一样,请前往【C++】:红黑树深度剖析 — 手撕红黑树!

RBTree.h

#include <iostream>
#include <assert.h>
using namespace std;//枚举颜色
enum Colour
{RED,BLACK
};//节点类
template <class T>
struct RBTreeNode
{T _data;RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;//pair<K, V> _kv;Colour _col;RBTreeNode(const T& data):_left(nullptr), _right(nullptr), _parent(nullptr), _data(data), _col(BLACK){}
};//迭代器类
template <class T, class Ref, class Ptr>
struct RBTreeIterator
{typedef RBTreeNode<T> Node;typedef RBTreeIterator<T, Ref, Ptr> Self;Node* _node;RBTreeIterator(Node* node):_node(node){}bool operator!=(const Self& s){return s._node != _node;}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}//从局部的某一个过程考虑Self& operator++(){if (_node->_right){//右不为空,右子树的最左节点就是下一个访问的节点Node* leftMost = _node->_right;while (leftMost->_left)leftMost = leftMost->_left;_node = leftMost;}else{//右为空,代表当前节点所在的子树已经访问完了,下一个访问的节点是祖先//沿着到根节点的那个路径查找,孩子是父亲左的那个祖先节点就是下一个访问的节点Node* cur = _node;Node* parent = cur->_parent;//循环找祖先while (parent && cur == parent->_right){cur = parent;parent = parent->_parent;}_node = parent;}return *this;}
};//K 是给find,erase用的,T 是给节点,插入用的
// KeyOfT 是由于下面需要比较,但是比较时不知道T的类型,
// set是key类型的比较,map是pair类型的比较,要统一变成key的比较template <class K, class T, class KeyOfT>
class RBTree
{typedef RBTreeNode<T> Node;
public:typedef RBTreeIterator<T, T&, T*> Iterator;typedef RBTreeIterator<T, const T&, const T*> ConstIterator;//中序遍历,找树的最左节点Iterator Begin(){//Node* cur = _root;Node* leftMost = _root;while (leftMost->_left)leftMost = leftMost->_left;return Iterator(leftMost);}Iterator End(){return Iterator(nullptr);}ConstIterator Begin()const{//Node* cur = _root;Node* leftMost = _root;while (leftMost->_left)leftMost = leftMost->_left;return ConstIterator(leftMost);}ConstIterator End()const{return ConstIterator(nullptr);}Iterator Find(const K& key){Node* cur = _root;while (cur){if (cur->_data > key)cur = cur->_left;else if (cur->_data < key)cur = cur->_right;elsereturn Iterator(cur);}return End();}pair<Iterator, bool> Insert(const T& data){if (_root == nullptr){_root = new Node(data);return make_pair(Iterator(_root), true);}KeyOfT kot;Node* cur = _root;Node* parent = nullptr;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;}elsereturn make_pair(Iterator(cur), false);}cur = new Node(data);Node* newnode = cur;//新增节点,颜色为红色cur->_col = RED;//此处省略变色+旋转部分的代码……private:Node* _root = nullptr;};

六,set 的封装实现

set的底层为红黑树,因此只需在set内部封装一棵红黑树,即可将该容器实现出来。

为了解决 set 中 key 值不能修改的问题,在传给 RBTree 的第二个模板参数前加 const 即可

MySet.h

#include "RBTree.h"namespace cc
{template<class K>class set{// 获取 set 里面的 keystruct SetOfT{const K& operator()(const K& key){return key;}};public:typedef typename RBTree<K, const K, SetOfT>::Iterator iterator;typedef typename RBTree<K, const K, SetOfT>::ConstIterator 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);}iterator find(const K& key){return _t.Find(key);}private:RBTree<K, const K, SetOfT> _t;};
}//使用 const 迭代器 
void Print(const cc::set<int>& s)
{auto it = s.begin();while (it != s.end()){cout << *it << " ";++it;}cout << endl;
}//测试代码
void Test_set()
{//int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };int a[] = { 16,3,7,11,9,26,18,14,15 };cc::set<int> s;for (auto e : a)s.insert(e);for (auto e : s)cout << e << " ";cout << endl;Print(s);
}

在这里插入图片描述

七,map 的封装实现

map的底层结构就是红黑树,因此在map中直接封装一棵红黑树,然后将其接口包装下即可。

map 中 pair 的 first 不能修改,second 可以修改,在 pair 的第一个参数前加 const 即可

MyMap.h

#include "RBTree.h"namespace cc
{template<class K, class V>class map{//获取 pair 中的 keystruct MapOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};public:typedef typename RBTree<K, pair<const K, V>, MapOfT>::Iterator iterator;typedef typename RBTree<K, pair<const K, V>, MapOfT>::ConstIterator 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 pair<K, V>& kv){return _t.Insert(kv);}iterator find(const K& key){return _t.Find(key);}//给一个key,返回value的引用V& operator[](const K& key){pair<iterator, bool> ret = insert(make_pair(key, V()));return ret.first->second;}private:RBTree<K, pair<const K,V>, MapOfT> _t;};
}//测试代码
void Test_map()
{cc::map<string, string> dict;dict.insert({ "left","左边" });dict.insert({ "right","右边" });dict.insert({ "insert","插入" });dict["left"] = "剩余,左边";cc::map<string, string>::iterator it = dict.begin();while (it != dict.end()){//it->first += 'x'; //errit->second += 'y'; //okcout << it->first << ":" << it->second << endl;++it;}cout << endl;
}

在这里插入图片描述

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

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

相关文章

Java OpenCV 图像处理41 图形图像 图片缩放

Java OpenCV 图像处理41 图形图像 图片缩放 1 图片缩放2 仿射变换3 透视变换 1 图片缩放 Java OpenCV 代码 OpenCV 提供的主要图像缩放函数&#xff0c;可以指定缩放比例或者目标尺寸。 Imgproc.resize(src, dst, new Size(width, height), fx, fy, interpolation);Imgproc.r…

科学又省力 宠物浮毛怎么去掉便捷高效?除毛秘籍养宠空气净化器

上次和朋友逛完街去她家&#xff0c;她家的猫哈基米一开门就飞奔过来&#xff0c;朋友直接抱起它狂亲。结果&#xff0c;猫毛和汗水粘得到处都是&#xff0c;手臂上、脸上都是&#xff0c;看得我这鼻炎星人直起鸡皮疙瘩。很多养宠物的朋友都说&#xff0c;天天给猫狗梳毛&#…

ProcessExplorer免费且功能强大的进程管理软件

ProcessExplorer是一款功能强大的进程管理软件&#xff0c;由Sysinternals开发&#xff0c;并被微软收购。它不仅可以管理和监控系统中的进程&#xff0c;还提供了许多实用的功能&#xff0c;如CPU和内存使用情况的曲线图表、DLL和句柄查看、进程冻结等。 安装ProcessExplorer…

微服务安全——OAuth2.1详解、授权码模式、SpringAuthorizationServer实战、SSO单点登录、Gateway整合OAuth2

文章目录 Spring Authorization Server介绍OAuth2.0协议介绍角色OAuth2.0协议的运行流程应用场景授权模式详解客户端模式密码模式授权码模式简化模式token刷新模式 OAuth 2.1 协议介绍授权码模式PKCE扩展设备授权码模式拓展授权模式 OpenID Connect 1.0协议Spring Authorizatio…

Axious的请求与响应

Axious的请求与响应 1.什么是Axious Axious是一个开源的可以用在浏览器和Node.js的异步通信框架&#xff0c;它的主要作用就是实现AJAX异步通信&#xff0c;其功能特点如下&#xff1a; 从浏览器中创建XMLHttpRequests ~从node.js创建Http请求 支持PromiseAPI 拦截请求和…

电信应用的振荡器基础知识

数字通信的最基本组成部分是同步。同步有很多方面。在数字传输中&#xff0c;同步是通过管理跨节点的平均传输和接收速率来管理无错误的传输和接收。在蜂窝通信中&#xff0c;同步使用户设备在移动中以及从一个小区移动到另一个小区时能够可靠地工作。在 5G 等先进网络中&#…

为什么w 和 b成同比例变化对超平面没有影响

文章目录 解释可视化证明数乘角度进行解释可视化代码领取 解释 在机器学习中&#xff0c;特别是支持向量机&#xff08;SVM&#xff09;和线性回归等模型中&#xff0c;参数 w w w和 b b b分别代表权重向量和偏置项。当说 w w w和 b b b成规模变化对超平面没有影响时&#xff…

pikachu靶场之目录遍历、敏感信息泄露

一、目录遍历 漏洞概述 在web功能设计中,很多时候我们会要将需要访问的文件定义成变量&#xff0c;从而让前端的功能便的更加灵活。 当用户发起一个前端的请求时&#xff0c;便会将请求的这个文件的值(比如文件名称)传递到后台&#xff0c;后台再执行其对应的文件。 在这个过…

邮件攻击案例系列二:冒充合作伙伴伪造发票商务邮件诈骗

案例描述 2023 年 11 月下旬&#xff0c;某知名外贸公司财务人员收到一封来自境外合作伙伴的邮件&#xff0c;说明有一张发票即将于 11 月 29 日到期的&#xff0c;希望该外贸公司能尽快付款。 该邮件有两个附件&#xff0c;一个附件是即将到期发票的电子版&#xff0c;一个附…

PHP8.3.9安装记录,Phpmyadmin访问提示缺少mysqli

ubuntu 22.0.4 腾讯云主机 下载好依赖 sudo apt update sudo apt install -y build-essential libxml2-dev libssl-dev libcurl4-openssl-dev pkg-config libbz2-dev libreadline-dev libicu-dev libsqlite3-dev libwebp-dev 下载php8.3.9安装包 nullhttps://www.php.net/d…

stable diffusion+LangChain+LLM自动生成图片

最近都在研究和学习stable diffusion和langchain的相关知识&#xff0c;并且看到stable diffusion也是有类似于ChatGLM的api调用方式&#xff0c;那在想有没有可能将stable diffusion也集成到langchain中来呢&#xff1f;看到网上资料比较多的是可以借助chatgpt来辅助stable di…

深度学习的前沿主题:GANs、自监督学习和Transformer模型

&#x1f48e; 欢迎大家互三&#xff1a;2的n次方_ &#x1f48e;1. 介绍 深度学习在人工智能领域中占据了重要地位&#xff0c;特别是生成对抗网络&#xff08;GANs&#xff09;、自监督学习和Transformer模型的出现&#xff0c;推动了图像生成、自然语言处理等多个领域的创…

【计算机网络】DHCP实验

一&#xff1a;实验目的 1&#xff1a;深入理解DHCP&#xff08;动态主机配置协议&#xff09;的工作原理和数据包交换过程。 2&#xff1a;掌握如何通过命令行释放和重新获取IP地址&#xff0c;并通过抓包软件分析DHCP消息的具体内容。 二&#xff1a;实验仪器设备及软件 硬…

什么是死锁,原子性

20240727 一、什么是死锁原子性 一、什么是死锁 原子性

CentOS7下操作iptables防火墙和firewalld防火墙

CentOS7下操作iptables防火墙和firewalld防火墙 &#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、…

小程序的运营方法:从入门到精通

随着科技的快速发展&#xff0c;小程序已成为我们日常生活和工作中不可或缺的一部分。小程序无需下载安装&#xff0c;即用即走的特点深受用户喜爱。那么&#xff0c;如何运营好一个小程序呢&#xff1f;下面就为大家分享一些小程序的运营方法。 一、明确目标用户 在运营小程序…

学习硬件测试01:串口下载+结构体封装说明+程序框架思想+程序框架的搭建+硬件测试程序(P42~P46)

一、串口下载 1.1引入 串口下载就是说用串口来烧录 STM32 的程序。 原因&#xff1a;当调试口&#xff08;SWD&#xff09;因为IO口没有设置好等原因被锁定而使用不了时&#xff0c;就需要用串口来更新程序。 1.2如何通过串口烧录程序&#xff1f; 1、选择串口&#xff1a…

轻松上手的订单管理系统推荐

本文精选了十款订单管理利器&#xff1a;纷享销客、Zoho CRM、简道云ERP、易订货、盘古云ERP、Cin7 Core、畅捷通T、Salesforce Commerce Cloud、NetSuite、浪潮GS。 市场上有各种各样的订单管理系统&#xff0c;每个看起来功能强大&#xff0c;但到底哪个最适合你的业务需求呢…

mysql的MHA以及故障模拟

目录 MHA概念 MHA的组件 MHA的特点 实验&#xff1a;搭建完成MHA的架构 实验&#xff1a;主备切换 实验结果 实验&#xff1a;故障切换 实验&#xff1a;故障恢复 MHA概念 MHA&#xff1a;高可用模式下的故障切换&#xff0c;基于主从复制。它解决的是单点故障和主从复…

C语言笔记36 •双链表•

1.双向链表的结构 Ps&#xff1a;这⾥的“带头”跟前⾯说的“头节点”是两个概念&#xff0c;实际前⾯的在单链表阶段称呼不严谨&#xff0c;但是为了我们更好的理解就直接称为单链表的头节点。带头链表⾥的头节点&#xff0c;实际为“哨兵位”&#xff0c;哨兵位节点不存储任何…