【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的first
		const 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
	{
		// 取出key
		const 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.mfbz.cn/a/633486.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

资料防拷贝该如何实现?数据防拷贝的方法有哪些

数据安全和隐私保护成为企业和个人关注的重点。电脑中存储的资料往往包含了重要的商业机密、个人隐私或其他敏感信息。 因此&#xff0c;如何有效防止他人非法拷贝电脑资料&#xff0c;成为了一个亟待解决的问题。 本文将探讨数据防拷贝的方法&#xff0c;以帮助企业和个人保护…

22-LINUX--多线程and多进程TCP连接

一.TCP连接基础知识 1.套接字 所谓套接字(Socket)&#xff0c;就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端&#xff0c;提供了应用层进程利用网络协议交换数据的机制。从所处的地位来讲&#xff0c;套接字上联应用进程…

Map遍历、反射、GC

map的遍历 用foreach遍历 HashMap<Character,Integer> map new HashMap<>();map.put(A,2);map.put(B,3);map.put(C,3);for (Map.Entry<Character,Integer> entry: map.entrySet()) {char key entry.getKey();int value entry.getValue();System.out.prin…

CF451E: Devu and Flowers(容斥原理 + 考虑反面 + golang组合模版)

题目截图 题目翻译 题目分析 正难则反&#xff0c;考虑所有不符合的例子 由于n很小&#xff0c;所以可以状态压缩二进制遍历完全部不符合例子的组合 对于不符合的例子&#xff0c;假设其中第i个不符合&#xff0c;那么就消耗掉fi 1个球 以此类推&#xff0c;减剩下s2个球 这时…

Android正向开发实现客户端证书认证

前言 如果第三方模块被混淆,那hook方式均不能生效。这时就需要根据系统包去定位校验的函数,因此需要对安卓开发者是如何实现客户端证书校验的有一定了解,接下来就介绍这部分内容。 开发者实现客户端证书校验的本质是:证书/密钥 + 代码。 在形式上有:证书校验、公钥校验和…

Anthropic绘制出了大型语言模型的思维图:大型语言模型到底是如何工作

今天&#xff0c;我们报告了在理解人工智能模型的内部运作方面取得的重大进展。我们已经确定了如何在 Claude Sonnet&#xff08;我们部署的大型语言模型之一&#xff09;中表示数百万个概念。这是对现代生产级大型语言模型的首次详细了解。这种可解释性的发现将来可以帮助我们…

Hadoop 客户端 FileSystem加载过程

如何使用hadoop客户端 public class testCreate {public static void main(String[] args) throws IOException {System.setProperty("HADOOP_USER_NAME", "hdfs");String pathStr "/home/hdp/shanshajia";Path path new Path(pathStr);Confi…

AWS安全性身份和合规性之Artifact

AWS Artifact是对您很重要的与合规性相关的信息的首选中央资源。AWS Artifact是一项服务&#xff0c;提供了一系列用于安全合规的文档、报告和资源&#xff0c;以帮助用户满足其合规性和监管要求。它允许按需访问来自AWS和在AWS Marketplace上销售产品的ISV的安全性和合规性报告…

当他们在说业务的时候,到底在说什么

业务就是通过提供产品和服务给客户&#xff0c;以获取某种价值&#xff0c;形成业务闭环&#xff0c;并能自负盈亏。 文章会以生动形象的比喻来介绍业务到底是什么。 什么是业务&#xff1f; 业务&#xff0c;就像一场精彩的舞台剧&#xff0c;每个角色都有自己的任务和目标…

PHP生成二维码+二维码包含logo图片展示

composer require chillerlan/php-qrcode 用到的扩展自己安装&#xff08;注&#xff1a;只生成二维码只要开gd扩展就行&#xff09; 仅生成二维码看这个&#xff1a; use chillerlan\QRCode\QRCode;public function QRCode(){$qrcode new QRCode();$url "http://ww…

新建项目上传gitee

1.在项目根目录下打开黑窗口执行初始化 git init2.复制码云上新建仓库地址 3.本地仓库和远程仓库建立连接 远程仓库地址是之前复制的仓库地址&#xff0c;复制后直接在命令窗口中鼠标右键Paste即可在命令窗口粘贴出来 git remote add origin 远程仓库地址4.每次上传之前先更…

工厂模式(简单工厂模式+工厂模式)

工厂模式的目的就是将对象的创建过程隐藏起来&#xff0c;从而达到很高的灵活性&#xff0c;工厂模式分为三类&#xff1a; 简单工厂模式工厂方法模式抽象工厂模式 在没有工厂模式的时候就是&#xff0c;客户需要一辆马车&#xff0c;需要客户亲自去创建一辆马车&#xff0c;…

uniapp-自定义navigationBar

封装导航栏自定义组件 创建 nav-bar.vue <script setup>import {onReady} from dcloudio/uni-appimport {ref} from vue;const propsdefineProps([navBackgroundColor])const statusBarHeight ref()const navHeight ref()onReady(() > {uni.getSystemInfo({success…

Qt---录音

1.获取麦克风阵列&#xff1a; QList<QAudioDeviceInfo> infos QAudioDeviceInfo::availableDevices(QAudio::AudioInput);for (int i 0; i < infos.count(); i){qDebug() << infos.at(i).deviceName();} "麦克风阵列 (Realtek(R) Audio)" 2.QAudio…

利用开源工具创建WEBGIS应用

在本文中&#xff0c;我们将大致说明利用开源工具如何与服务器交互以构建交互式或动态 Web GIS。 WebGIS 应用程序已成为展示地理数据的重要模式。我们现在拥有允许用户交互的机制&#xff0c;以便用户可以选择数据&#xff0c;甚至修改或添加新数据。 什么是WEBGIS? 通过网络…

大创项目推荐 深度学习手势识别 - yolo python opencv cnn 机器视觉

文章目录 0 前言1 课题背景2 卷积神经网络2.1卷积层2.2 池化层2.3 激活函数2.4 全连接层2.5 使用tensorflow中keras模块实现卷积神经网络 3 YOLOV53.1 网络架构图3.2 输入端3.3 基准网络3.4 Neck网络3.5 Head输出层 4 数据集准备4.1 数据标注简介4.2 数据保存 5 模型训练5.1 修…

用markdown(typora)画系统框图或系统结构图

markdown本身是不支持画系统框图或系统结构图的&#xff1b;但是可以参考excel的语法格式&#xff0c;用合并单元格填充背景色&#xff0c;来实现我们预期的效果&#xff1b; 源代码是html语法&#xff0c;如果有其它需求也可以自己搜索html语法&#xff0c;进行优化 <ta…

netcat一键开始瑞士军刀模式(KALI工具系列五)

目录 1、KALI LINUX简介 2、netcat工具简介 3、在KALI中使用netcat 3.1 目标主机IP&#xff08;win&#xff09; 3.2 KALI的IP 4、命令示例 4.1 测试某IP的端口是否打开 4.2 TCP扫描 4.3 UDP扫描 4.4 端口刺探 4.5 直接扫描 5、即时通信 5.1 单击对话互联 5.2 传…

单向无头链表实现

目录 1. 为什么要有链表&#xff1f; 2. 链表的种类 3. 具体功能实现 &#xff08;1&#xff09;节点结构体定义 &#xff08;2&#xff09;申请节点 &#xff08;3&#xff09;尾插 &#xff08;4&#xff09;尾删 &#xff08;5&#xff09;头插 &#xff08;6&#…

文本信息的二维码怎么做?在线制作文本二维码的3个步骤

现在通过二维码来展示文本信息是很常见的一种方式&#xff0c;可以将信息编辑好排版后生成二维码&#xff0c;其他人可以通过扫描生成的二维码来获取文本信息。这种方式传递起来更加的简单快捷&#xff0c;而且二维码可以长期提供内容展示效果降低了推广成本&#xff0c;在很多…