【C++】模拟实现map和set(用红黑树进行封装)

模拟实现map和set

  • 前言
  • 正式开始
    • 简单框架
    • data的比较
    • 迭代器
    • operator++
    • operator-\-
    • [ ]重载

在这里插入图片描述

前言

本篇以前一篇红黑树模拟实现插入功能为基础:【C++】红黑树模拟实现插入功能(包含旋转和变色)

本篇中不会再讲解关于旋转和变色的知识。只是对于红黑树进行简单的封装。

如果你在此之前已经对于红黑树的旋转和变色很了解了,就不需要再看前一篇了,但若没了解过的话,建议先看看前一篇,前一篇中的内容懂了才能看下面的内容。

我先将前一篇中实现的代码放这,各位可以先不看,我讲到要修改的时候再看也不迟。

#pragma onceenum color
{RED,BLACK
};template<class K, class V>
struct RBTreeNode
{RBTreeNode(const pair<K, V>& kv = make_pair(K(), V())):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv){}RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;pair<K, V> _kv;color _col;
};template<class K, class V>
class RBTree
{typedef RBTreeNode<K, V> Node;public:bool Insert(const pair<K, V>& kv){// 树若为空,就让根直接指向新节点,再让根节点的颜色变为黑色if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;return true;}// 找到合适的插入位置Node* cur = _root;Node* parent = nullptr;while (cur){if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else{return false;}}// 插入后再连接起来cur = new Node(kv);if (kv.first < parent->_kv.first)parent->_left = cur;else if (kv.first > parent->_kv.first)parent->_right = cur;elseassert(false);cur->_parent = parent;// 插入结束,开始调整树结构cur->_col = RED; // 插入节点一定要为红色while (parent && parent->_col == RED) // 父节点存在且为红时才需要调整。{// 当父节点为红色时,爷爷节点一定为黑色Node* grandParent = parent->_parent;assert(grandParent);assert(grandParent->_col == BLACK);Node* uncle = nullptr;if (parent == grandParent->_left)uncle = grandParent->_right;elseuncle = grandParent->_left;if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandParent->_col = RED;cur = grandParent;parent = cur->_parent;}else // uncle不存在 或 uncle存在且为黑{if (parent == grandParent->_left){if (cur == parent->_left) // 单边 左左 ==》右单旋{RotateR(grandParent);parent->_col = BLACK;grandParent->_col = RED;}else // 左 右 ==》左右双旋{RotateL(parent);RotateR(grandParent);cur->_col = BLACK;grandParent->_col = RED;}}else // parent == grandParent->_right{if (cur == parent->_right) // 单边 右右 ==》左单旋{RotateL(grandParent);parent->_col = BLACK;grandParent->_col = RED;}else // 右左 ==》右左双旋{RotateR(parent);RotateL(grandParent);cur->_col = BLACK;grandParent->_col = RED;}}// 只要旋转过后就平衡了break;}}_root->_col = BLACK;return true;}void InOrder(){_InOrder(_root);cout << endl;}bool IsBalance(){// 空树是平衡的if (_root == nullptr)return true;// 根为黑(第一条)if (_root->_col == RED)return false;// 找到最左边路径中黑色节点个数// 以该个数为基准,用来比较其他路径的黑色节点Node* cur = _root;int blackNum = 0;while (cur){if (cur->_col == BLACK)++blackNum;cur = cur->_left;}// 判断二三四条return _IsBalance(_root, 0, blackNum);}private:bool _IsBalance(Node* root, int blackCount, int& blackNum){if (root == nullptr){// 判断当前路径黑节点个数是否与其他相同if (blackCount != blackNum)return false;elsereturn true;}//  是黑色节点就让blackCount++if (root->_col == BLACK)++blackCount;// 不能有连续黑色节点if (root->_col == RED && root->_parent->_col == RED)return false;// 节点非黑即红if (root->_col != BLACK && root->_col != RED)return false;// 继续左右树return _IsBalance(root->_left, blackCount, blackNum)&& _IsBalance(root->_right, blackCount, blackNum);}void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_kv.first << ":::" << root->_kv.second << endl;_InOrder(root->_right);}void RotateL(Node* parent){Node* ppNode = parent->_parent;Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;subR->_left = parent;parent->_parent = subR;if (ppNode){if (ppNode->_left == parent)ppNode->_left = subR;elseppNode->_right = subR;subR->_parent = ppNode;}else{_root = subR;subR->_parent = nullptr;}}void RotateR(Node* parent){Node* ppNode = parent->_parent;Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;subL->_right = parent;parent->_parent = subL;subL->_parent = ppNode;if (ppNode){if (ppNode->_left == parent)ppNode->_left = subL;elseppNode->_right = subL;}else{_root = subL;subL->_parent = nullptr;}}private:Node* _root = nullptr;
};

正式开始

首先,STL库中map和set底层就用的是红黑树,但是map是k/v模型的,set是k模型的,库中是实现了两棵红黑树吗?

不是的,STL库是非常注重代码的复用性的,不会说实现两棵红黑树的。

我们自己模拟实现之前先来看看STL库中是样搞的:

对于map和set另个头文件而言:
在这里插入图片描述

对于stl_tree.h而言:

红黑树的节点颜色:
在这里插入图片描述
这里用的是bool类型的,0表示RED,1表示BLACK。我前一篇的模拟实现中是用枚举来搞的,和这里不太一样,但是都可以。

树节点:
在这里插入图片描述
在这里插入图片描述
库里面是先实现了一个除存放值以外的指针等节点内容的结构体__rb_tree_node_base,然后再搞了一个结构体__rb_tree_node来继承前一个结构体,在__rb_tree_node内部定义值域,并用模版来使这个值域泛型化,从而使得当模版参数传的是k模型的时候就是set,传pair的时候就是map。

树:
在这里插入图片描述

这样就实现出了一棵泛型结构的rbtree,通过不同实例化参数,实现出map和set。

对于stl_set.h和stl_map.h而言:
在这里插入图片描述

这样就可以动手搞了。

为了和库中的map和set区别,我就将模拟实现的文件命名为 Map.h 和 Set.h 。

简单框架

先把红黑树改改。
我前面直接模拟实现的是 key/value 模型的,但是set是k模型的,所以要将存储的数据类型改成模版,而非直接是pair。那么我就直接用T来表示了。

树节点:
在这里插入图片描述

树:
在这里插入图片描述

要改的地方不多,就是把所有原来用到模版V的地方改为T,然后将用到kv的地方改为data就好了。

上面我还圈出了用data直接比较的地方,这里直接用大于小于号进行比较是错误的,因为不知道data的类型是key还是key/value,所以直接比的话就出问题了。库中虽然重载了pair的大于小于号,但是并不是我们想用的,我们是想直接比较first就行了, 库中的是比较完first还会比second,所以我们得另寻他路。但是这里还不能讲,得等会在Map.h和Set.h中写点东西才能改。

先给Map和Set打框架:

Map

在这里插入图片描述

Set

在这里插入图片描述

可能有同学要问,为啥还要有第一个模版参数K呢?

因为插入元素或者查找元素的时候都需要用到key,而当我们只搞一个V时,就会导致map传
pair的时候没法得到key的类型而导致无法比较。

我刚刚也提到了直接用data比较会导致错误,现在我们来解决一下。

data的比较

我们可以参考一下库中是怎么搞的。
在这里插入图片描述
库中是用第三个模版参数来解决这个问题的,库里面的原理就不说了,我这里直接实现。

第三个参数是仿函数,KeyOfValue,意思就是提取中value中的key,对于set而言可以直接接将仿函数的返回值给其value,因为set的value就是key。而map则需要返回其元素对应pair
中的key。

所以对于set而言:
在这里插入图片描述

对于map而言:
在这里插入图片描述

上面传KeyOfValue的用法是将data传给()重载,map的data就是pair,set的data就是key,然后就返回对应的key值。

然后再改一下红黑树中用到data的地方:

先在用到data比较的函数中定义一个KeyOfValue的对象:
在这里插入图片描述
然后再给每一个用到data且进行比较的地方都添上kov()
在这里插入图片描述
这样就好了。

我们在map和set中封装一下insert:
在这里插入图片描述

在这里插入图片描述

测试一下:

map:
在这里插入图片描述

set:
在这里插入图片描述

迭代器

上面我们没法直接打印信息,只能通过调试简单看一下,因为迭代器还没实现。

下面就搞搞迭代器。

还是,红黑树的迭代器就是map和set的迭代器。而红黑树迭代器的实现可以参考参考链表的迭代器。模版参数也是T, Ref, Ptr这三个。

基本框架:
在这里插入图片描述

迭代器,无非那几个功能。
我们这里先实现一下*、->、==、!=。

在这里插入图片描述

==传参的时候要传一个迭代器,类型太长了,重命名一下:
在这里插入图片描述

在这里插入图片描述

然后在RBTree中封装一下:
在这里插入图片描述
写begin和end时要确定一下begin和end分别指向哪里。

库中是这样搞的:
在这里插入图片描述
上面的header就相当于是双向带头循环链表中的哨兵位头结点,begin就是树最左侧的节点,end就是树最右侧的节点。begin的话就是header->_left,end就是header->_right。

但是我就不搞那个header了,不搞也是可以实现的:
在这里插入图片描述

那么这里begin就是最左侧的节点,end给空指针就行。
在这里插入图片描述

在Map中我们需也要封装一下其迭代器:
在这里插入图片描述
注意上面用到了typename关键字,就是为了标识出红黑树中的iterator是一个类型名,而非静态成员,因为静态成员也可直接通过类域访问。

还需要加上begin和end:
在这里插入图片描述

然后距离迭代器遍历整棵树还差一步,就是++和--。其实++就够了。

operator++

这里要稍微想一想。

红黑树遍历就指的是中序遍历,这样打印出来的东西是有序的。

但是我们这里不是遍历了,而是访问一个节点一个节点的走。怎样实现呢?

想一想,中序遍历,访问到一个节点是,该节点的左子树一定已经访问过了,此时访问该节点之后,就要访问右子树了。

那么右子树可以分两种情况。

非空

非空的话,按照中序遍历的顺序,应该是直接跑到右子树中最左边的节点。

空的话,按照中序遍历的顺序,应该跑到其祖先路径上未被访问过的节点。如果祖先路径都被访问过了,那么就是这棵树已经遍历完了。
在这里插入图片描述
想要控制这一点的话,就得不断向上寻找到一个祖先里面孩子不是祖先的右子树的那个祖先节点。因为左根右的顺序,往上寻找的话,根的右一定已经访问过了,所以就是要找根的左的节点。
.
如上图中的7。5、6均已在7之前访问过了,当前为7的话,++就应该跑到8的位置,也就是说不断向祖先节点寻找,7是6的右,6被访问过了,继续看8,6是8的左,8未被访问,就该跑到8的位置了。
.
而图中的15,一路向上,都是父的右,直到8上面为空时,找不到符合条件的祖先节点了。此时就应该停止,operator++返回空指针对应的节点即可。

那我们就按照上面的逻辑实现一下:
在这里插入图片描述

测试:
在这里插入图片描述

再来看看set:
在这里插入图片描述

在这里插入图片描述

operator--

--的话,就是++倒着来就行。

逻辑反过来,看左子树就行。

实现一下:
在这里插入图片描述

[ ]重载

再来说一下[ ]重载。

这个对于map来说非常有用。也只有map能用,前面map和set介绍的那一篇我也讲了,[ ]的返回值,这里不再细说了,不懂的同学点传送门:【C++】STL map和set用法基本介绍。

首先我们要把红黑树中的insert返回值改一下。改成pair<iterator, bool>,然后内部返回值的细节再改改:

在这里插入图片描述

然后map和set中的封装也要改:
在这里插入图片描述

然后再在map中重载一下[ ]。
在这里插入图片描述

测试:

迭代器走一遍:
在这里插入图片描述

范围for走一遍:
在这里插入图片描述

erase还是不讲,本篇就只讲一下插入对应的封装。迭代器中有些功能也可以实现但这里没有给,比如说后置++、--等等。红黑树中的find等。如果感兴趣的同学可自行查找资料进行学习。

到此结束。。。

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

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

相关文章

Mongoose http server 例子

今天抽了点时间看了一下 mongoose的源码&#xff0c; github 地址&#xff0c;发现跟以前公司内部使用的不太一样&#xff0c;这里正好利用其 http server 例子来看一下。以前的 http message 结构体是这样的&#xff1a; /* HTTP message */ struct http_message {struct mg_…

用Python编写的小游戏:探索游戏世界的乐趣

探索开始 引言&#xff1a;第一部分&#xff1a;猜数字游戏代码案例1&#xff1a; 第二部分&#xff1a;石头剪刀布游戏代码案例2&#xff1a; 第三部分&#xff1a;迷宫游戏代码案例3&#xff1a; 总结&#xff1a; 引言&#xff1a; Python是一种简单易学的编程语言&#xf…

CSS:服务器字体 与 响应式布局(用法 + 例子 + 效果)

文章目录 服务器字体定义 服务器字体使用例子 响应式布局设备类型设备特性例子 服务器字体 解决字体不一致而产生的。 首先&#xff0c;在网上把字体下载好。 定义 服务器字体 font-face{font-family:字体名称;src:url(字体资源路径); }使用 在需要使用的选择器里加上 font…

探索数据之美:初步学习 Python 柱状图绘制

文章目录 一 基础柱状图1.1 创建简单柱状图1.2 反转x和y轴1.3 数值标签在右侧1.4 演示结果 二 基础时间线柱状图2.1 创建时间线2.2 时间线主题设置取值表2.3 演示结果 三 GDP动态柱状图绘制3.1 需求分析3.2 数据文件内容3.3 列表排序方法3.4 参考代码3.5 运行结果 一 基础柱状图…

c++ 学习系列 -- 智能指针

一 为什么引入智能指针&#xff1f;解决了什么问题&#xff1f; C 程序设计中使用堆内存是非常频繁的操作&#xff0c;堆内存的申请和释放都由程序员自己管理。但使用普通指针&#xff0c;容易造成内存泄露&#xff08;忘记释放&#xff09;、二次释放、程序发生异常时内存泄…

Remote Sensing,2023 | 基于SBL的分布式毫米波相干雷达成像的高效实现

Remote Sensing,2023 | 基于SBL的分布式毫米波相干雷达成像的高效实现 注1&#xff1a;本文系“无线感知论文速递”系列之一&#xff0c;致力于简洁清晰完整地介绍、解读无线感知领域最新的顶会/顶刊论文(包括但不限于 Nature/Science及其子刊; MobiCom, Sigcom, MobiSys, NSDI…

【对于一维信号的匹配】对一个一维(时间)信号y使用自定义基B执行匹配追踪(MP)研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Tesseract用OpenCV进行文本检测

我没有混日子&#xff0c;只是辛苦的时候没人看到罢了 一、什么是Tesseract Tesseract是一个开源的OCR&#xff08;Optical Character Recognition&#xff09;引擎&#xff0c;OCR是一种技术&#xff0c;它可以识别和解析图像中的文本内容&#xff0c;使计算机能够理解并处理…

【LeetCode】617.合并二叉树

题目 给你两棵二叉树&#xff1a; root1 和 root2 。 想象一下&#xff0c;当你将其中一棵覆盖到另一棵之上时&#xff0c;两棵树上的一些节点将会重叠&#xff08;而另一些不会&#xff09;。你需要将这两棵树合并成一棵新二叉树。合并的规则是&#xff1a;如果两个节点重叠…

k8s常用资源管理 控制

目录 Pod&#xff08;容器组&#xff09;&#xff1a;Pod是Kubernetes中最小的部署单元&#xff0c;可以包含一个或多个容器。Pod提供了一种逻辑上的封装&#xff0c;使得容器可以一起共享网络和存储资源 1、创建一个pod 2、pod管理 pod操作 目录 创建Pod会很慢 Pod&…

Baumer工业相机堡盟工业相机如何通过BGAPI SDK设置相机的固定帧率(C++)

Baumer工业相机堡盟工业相机如何通过BGAPI SDK设置相机的固定帧率&#xff08;C&#xff09; Baumer工业相机Baumer工业相机的固定帧率功能的技术背景CameraExplorer如何查看相机固定帧率功能在BGAPI SDK里通过函数设置相机固定帧率 Baumer工业相机通过BGAPI SDK设置相机固定帧…

【图像去噪的滤波器】非局部均值滤波器的实现,用于鲁棒的图像去噪研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…