c++编程(24)——map的模拟实现

news2024/9/27 17:31:01

欢迎来到博主的专栏:c++编程
博主ID:代码小号

文章目录

    • map的底层
      • 红黑树的节点
    • map的模拟实现
      • map的查找与插入
      • map的迭代器

map的底层

map的底层是一个红黑树,关于红黑树的章节博主写在了数据结构专栏当中,因此不再赘述。

template<class key,class T,class keyofT,class valueofT>
class RBtree
{
public:
	RBtree()
		:_root(nullptr)
	{}
	typedef RBtreeNode<T> Node;
	typedef RBtreeIterator<T> iterator;
	typedef const RBtreeIterator<T> const_iterator;

	pair<iterator,bool> find(const key& key)
	{
		Node* cur = _root;
		while (cur != nullptr)
		{
			if (key < kot(cur->_data))
				cur = cur->_left;
			else if (key > kot(cur->_data))
				cur = cur->_right;
			else
				return make_pair(iterator(_root,cur),true);
		}
		return make_pair(iterator(_root,nullptr),false);
	}

	pair<iterator,bool> insert(const T& data)
	{
		Node* newnode = new Node(data);
		if (_root == nullptr)
		{
			_root = newnode;
			_root->_col = BLACK;
			return make_pair(iterator(_root,_root), true);
		}
		Node* cur = _root;
		Node* parent = _root;
		while (cur != nullptr)
		{
			if (kot(newnode->_data) < kot(cur->_data))
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (kot(newnode->_data) >kot( cur->_data))
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return make_pair(iterator(_root,cur), false);
			}
		}

		if (kot(newnode->_data) > kot(parent->_data))
		{
			newnode->_parent = parent;
			parent->_right = newnode;
		}
		else
		{
			newnode->_parent = parent;
			parent->_left = newnode;
		}
		cur = newnode;
		keep_balance(parent, cur);
		return make_pair(iterator(_root,cur), true);
	}
//省略
	private:

		Node* _root;
		keyofT kot;
		valueofT vot;
	};

为了与标准库中函数原型相同,博主将inseet和find函数的返回类型修改成了pair<iterator,bool>。

红黑树的节点

enum colour
{
	RED,BLACK
};
template<class T>
struct RBtreeNode
{
	RBtreeNode(const T& data)
		:_data(data)
		,_right(nullptr)
		,_left(nullptr)
		,_parent(nullptr)
		,_col(RED)
	{}
	T _data;//值
	RBtreeNode* _right;//右子树
	RBtreeNode* _left;//左子树
	RBtreeNode* _parent;//父节点
	colour _col;//颜色
};

与在数据结构中写的红黑树不同,为了让红黑树能够同时称为set和map的底层,博主不再使用pair<key,value>作为节点数据的类型,而是使用T作为泛型,这是因为set的层是key型的红黑树,而map是key-value型的红黑树,这两者之间的区别就在于节点存的数据的类型。如果用pair<key,value>作为红黑树的节点数据类型,那就不能适配set,因此采用泛型T。

map的模拟实现

template<class key,class value>
class map
{
public:
	typedef pair<key, value> value_type;

private:
	RBtree<key, value_type, mapkeyofT,valueofT> _t;
};

map需要key值映射value值,因此需要实例化出pair<key,value>类型的红黑树底层。

template<class key,class T,class keyofT,class valueofT>
class RBtree
{
public:
	RBtree()
		:_root(nullptr)
	{}
//省略
	private:

		Node* _root;
		keyofT kot;
		valueofT vot;
	};

在底层RBtree中,有两个私有成员需要注意,kot是key值提取器,vot是value值提取器,为什么要做出这种设计呢?我们以函数insert为例。

pair<iterator,bool> insert(const T& data)
{
	Node* newnode = new Node(data);
	if (_root == nullptr)
	{
		_root = newnode;
		_root->_col = BLACK;
		return make_pair(iterator(_root,_root), true);
	}
	Node* cur = _root;
	Node* parent = _root;
	while (cur != nullptr)
	{
		if (kot(newnode->_data) < kot(cur->_data))
		{
			parent = cur;
			cur = cur->_left;
		}
		else if (kot(newnode->_data) >kot( cur->_data))
		{
			parent = cur;
			cur = cur->_right;
		}
		else
		{
			return make_pair(iterator(_root,cur), false);
		}
	}

	if (kot(newnode->_data) > kot(parent->_data))
	{
		newnode->_parent = parent;
		parent->_right = newnode;
	}
	else
	{
		newnode->_parent = parent;
		parent->_left = newnode;
	}
	cur = newnode;
	keep_balance(parent, cur);
	return make_pair(iterator(_root,cur), true);
}

由于RBtree不仅用于map容器,还需要用于set容器,而set容器的_data只有一个key值,因此使用key值进行比较大小非常方便,而map的_data却是pair<key,value>类型,pair类型对象之间是不支持比较大小的(虽然标准库中也允许pair之间的对象比较大小,但却不是key值之间的比较),因此我们需要将data的key值提取出来。

key值提取器定义在map当中,实现如下:

struct mapkeyofT
{
	const key& operator()(const pair<key,value>& data)
	{
		return data.first;
	}
};

这样我们使用kot(data)时,得到的是data当中的first成员,即key值,而vot则是提取data的second,即value值。

struct valueofT
{
	const value& operator()(const pair<key,value>& data)
	{
		return data.second;
	}
};

map的查找与插入

由于RBtree底层已经设计好了insert和find函数,我们只需要复用RBtree中的对应函数就行,这里很简单。

pair<iterator,bool> insert(const value_type& data)
{
	return _t.insert(data);
}
pair<iterator, bool> find(const key& key)
{
	return _t.find(key);
}

map的迭代器

红黑树的迭代器设计就比较麻烦了,RBtree的迭代器是一个双向迭代器,支持前进操作operator++,和后退操作operator--,前进与后退的行为与二叉搜索树的中序遍历完全一致。因此map的实现的难点完全在这里(因为红黑树的底层设计在之前的博客就已经完成了)。

struct RBtreeIterator
{
	typedef RBtreeNode<T> Node;
	typedef RBtreeIterator<T> Self;
	typedef T value_type;
	RBtreeIterator(Node* root=nullptr,Node* node=nullptr)
		:_root(root)
		,_node(node)
	{}

	Node* _root;
	Node* _node;
};

RBtree的迭代器需要存储两个值,一个是存储迭代器指向的树_root,另外一个则是存储指向_root的节点_node。

begin()函数返回_root的最左边的节点,这是因为二叉搜索树中序遍历的第一个节点就是此节点,因此我们将其设置成起始位置begin。
在这里插入图片描述
而c++规定,end()返回的迭代器需要不能指向有效节点,那么我们返回一个空节点(nullptr)即可(实际STL库中并非如此)。

至于operator!=等操作,比较简单,博主直接给上代码。

	template<class T>
	struct RBtreeIterator
	{
		typedef RBtreeNode<T> Node;
		typedef RBtreeIterator<T> Self;
		typedef T value_type;
		RBtreeIterator(Node* root=nullptr,Node* node=nullptr)
			:_root(root)
			,_node(node)
		{}

		const Self& operator=(const RBtreeIterator it)const
		{
			_root = it._root;
			_node = it._node;
		}

		value_type& operator*()
		{
			return _node->_data;
		}

		value_type* operator->()
		{
			return &(_node->_data);
		}

		Self& begin()
		{
			Node* y = _root;
			while(y && y->_left != nullptr)
			{
				y = y->_left;
			}
			_node = y;
			return *this;
		}

		const Self& begin() const
		{
			Node* y = _root;
			while (y && y->_left != nullptr)
			{
				y = y->_left;
			}
			_node = y;
			return *this;
		}

		Self& end()
		{
			_node = nullptr;
			return *this;
		}

		bool operator!=(Self& it)const
		{
			return _node != it._node;
		}

		Node* _root;
		Node* _node;
	};

接下来就是重点的operator++和operator–操作了,前进的操作需要按照中序遍历的规则。因此我们先来顺一下二叉树的中序遍历规律吧。

情况(1),当前节点的右子树存在时。
在这里插入图片描述

根据中序遍历的规则,如果右子树存在,那么下一个遍历的节点就会在右子树的最左节点
在这里插入图片描述

情况2,如果当前节点不存在右子树,且是父节点的左节点。
在这里插入图片描述
那么下一个遍历的节点就会回溯到其父节点。
在这里插入图片描述

情况3,如果当前节点不存在右子树,而且是父节点的右节点
在这里插入图片描述
那么就一直回溯,回溯到当前节点是父节点的左子树的父节点处(有点绕,但确实是这样)。
在这里插入图片描述
代码如下:

	const Self& operator++()
	{
		assert(_node);
		Node* y = nullptr;
		if (_node->_right != nullptr)
		{
			y = _node->_right;
			while (y->_left != nullptr)
			{
				y = y->_left;
			}
			_node = y;
		}
		else
		{
			Node* parent = _node->_parent;
			if (parent->_left == _node)
				_node = parent;
			else
			{
				y = _node;
				while (parent&&y == parent->_right)
				{
					y = parent;
					parent = y->_parent;
				}
				_node = parent;
			}
		}
		return RBtreeIterator(_root, _node);
	}

而后退操作(operator--)则是反过来,我们来看看迭代器是如何实现后退操作的:
情况(1)如果当前节点是空节点(如果是end()返回的迭代器就是空节点)
那么我们就找到搜索二叉树的最右节点。
在这里插入图片描述

情况2,如果当前节点的左子树存在。
在这里插入图片描述
那么我们寻找左子树的最右节点。
在这里插入图片描述
情况(3)当前节点的左子树不存在。
在这里插入图片描述
那么我们就回溯到当前节点是父节点的右子节点的父节点处。
在这里插入图片描述
代码如下:

const Self& operator--()
{
	Node* y = nullptr;
	if (_node == nullptr)//end()
	{
		y = _root;
		while (y && y->_right)
		{
			y = y->_right;
		}
		_node = y;
	}
	else if (_node->_left!=nullptr)
	{
		y = _node->_left;
		while (y->_right != nullptr)
		{
			y = y->_right;
		}
		_node = y;
	}
	else
	{
		y = _node;
		Node* parent = y->_parent;
		while (parent->_left == y)
		{
			y = parent;
			parent = y->_parent;
		}
		_node = parent;
	}
	return *this;
}

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

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

相关文章

网络安全服务基础Windows--第8节-DHCP部署与安全

DHCP协议理解 定义&#xff1a;DHCP&#xff1a;Dynamic Host Configuration Protocol&#xff0c;动态主机配置协议&#xff0c;是⼀个应⽤在局域⽹中的⽹络协议&#xff0c;它使⽤UDP协议⼯作。 67&#xff08;DHCP服务器&#xff09;和68&#xff08;DHCP客户端&#xff0…

C语言:常用技巧及误用

一、字符串存储在数组中 int main() {char* arr[7] {"xiaoming","zhangsan","李四"};printf("%s\n", arr[0]);printf("%s\n", arr[2]);return 0; } 二、scanf()函数用法 2.1 scanf()输入字符串 int main() {char arr[10…

raksmart香港大带宽服务器地址

RAKsmart香港大带宽服务器的地址是由RAKsmart公司提供的香港机房所在地&#xff0c;具体地址未在公开资料中披露&#xff0c;但其主要特点是提供高带宽且不限制流量的服务。 RAKsmart是一家成立于2012年的美国公司&#xff0c;其香港机房以提供大带宽、直连内地的优化线路和丰富…

wincc 远程和PLC通讯方案

有 5个污水厂 的数据需要集中监控到1个组态软件上,软件是WINCC。每个污水厂监控系统都是独立的&#xff0c;已经投入运行了&#xff0c; 分站也是WINCC 和西门子PLC 。采用巨控远程模块的话&#xff0c;有两种方式&#xff1a;一种是从现场的PLC取数据&#xff0c;一种是从分站…

HubliderX将Vue3离线包打包生成App,以及解决打包后的APP出现白屏的问题(简单示例)

一、准备 HBuilderX官网&#xff0c;先去官网下载需要的工具到vue项目中把rooter的模式由“history”改为“hash”&#xff0c;否则在本地真机调试时会出现白屏 更改 vue.config.js文件&#xff0c;不修改的话&#xff0c;同样会出现白屏&#xff08;原因&#xff1a;app打开需…

java对接斑马打印机打印标签

JAVA对接斑马打印机打印RFID标签和普通标签 1、打印RFID标签 在打印RFID标签时&#xff0c;如果机器在没有校准的情况下进行打印标签&#xff0c;此时如果还需要获取到RFID的epc值&#xff0c;那么打印机返回的EPC值&#xff0c;有可能不是当前标签的epc值。考虑到此种情形&a…

技能 | next.js服务端渲染技术

哈喽小伙伴们大家好,我是程序媛小李,今天为大家分享一项前端开发中比较主流的服务端渲染技术:next.js 首先,next.js是什么? 通俗来讲,它就是一个React框架, 它能干啥?它能实现服务端渲染. 什么是服务端渲染? 一句话它就是在服务端生成整个页面的内容,用户在客户端只需要…

VS-E5PH3006L-N3 600V 30A 高效低损耗整流器 二极管 电动 / 混动汽车电池充电的可靠之选

VS-E5PH3006L-N3参数特性&#xff1a; 反向电压&#xff08;VR&#xff09;&#xff1a;600V&#xff0c;这表示该整流器在电路中能承受的最大反向电压为 600 伏特&#xff0c;超过此电压可能会导致器件损坏。平均整流电流&#xff08;IF (AV)&#xff09;&#xff1a;30A&…

测试流程及注意事项,包括jemter和postman

一、接口测试需要考虑的地方有哪些&#xff1f; 1、考虑输入参数和输出参数的合法性&#xff0c;参数必填&#xff0c;默认值&#xff0c;参数长度和格式校验&#xff0c;边界等&#xff0c;图片长传考虑图片大小和格式。查询考虑数据排序&#xff0c;分页考虑分页显示等。 2…

linux 下一跳缓存,early demux(‌早期解复用)‌介绍

3.6版本以后的下一跳缓存 3.6版本移除了FIB查找前的路由缓存。这意味着每一个接收发送的skb现在都必须要进行FIB查找了。这样的好处是现在查找路由的代价变得稳定(consistent)了。3.6版本实际上是将FIB查找缓存到了下一跳(fib_nh)结构上&#xff0c;也就是下一跳缓存下一跳缓存…

【算法】蒙特卡洛模拟

一、引言 蒙特卡洛模拟算法是一种基于概率和统计理论的数值计算方法&#xff0c;通过随机抽样来近似复杂系统的概率问题。它以摩纳哥著名的赌场蒙特卡洛命名&#xff0c;象征着其基于随机性的特点。 二、算法原理 蒙特卡洛模拟算法的核心思想是利用随机抽样来估计一个函数的期望…

【SQL】删除表中重复数据的方法

很久之前我写入一张sql的数据表&#xff0c;它里面有很多重复的内容。然后我想只保留一条原始数据&#xff1a; 例如上面的时间&#xff0c;出现了很多重复值。 我最初用的是这种方法&#xff1a; SELECT * FROM table_name WHERE primary_key IN (SELECT max(primary_key)F…

2.4 SQL注入之高权限注入下

SQL注入之高权限注入 1.注入流程与上节实例相同 查询所有数据库名称 http://localhost/sqli-labs-master/Less-2/?id-2%20union%20select%201,group_concat(schema_name),3%20from%20information_schema.schemata查询数据库对应的表名 http://localhost/sqli-labs-master/Le…

JMeter之接口测试

在做接口测试之前&#xff0c;我们起码需要了解&#xff1a; 1、接口涉及的业务 2、接口的基本信息&#xff1a;访问地址、传值方式&#xff08;Post 或 Get&#xff09;、协议类型、域名或IP、端口、参数 3、接口参数是否加密或者有其他处理加工 很多时候&#xff0c;可能…

U8+ 提示子票区间开始输入不合法处理

手工做是否分包流转为是的商业汇票&#xff0c;提示如下&#xff1a; 处理方法&#xff1a; 第一步&#xff1a; 第二步 数据类型为数字&#xff0c;保存即可&#xff0c;填写值为1

STM32H7 串口 空闲中断 硬件FIFO 任意长接收 Hal库 IDLE

STM32H7 串口 空闲中断 硬件FIFO 任意长接收 Hal库 IDLE 由于工作原因好久不接触ST的芯片了&#xff0c;所以断更ST的东西了&#xff0c;不过偶尔玩玩也挺好的。 接着上篇继续说串口的事儿&#xff0c;这次是FIFO&#xff0c;STM32H7的串口都是带硬件FIFO&#xff0c;大小是发…

java重点学习-redis

一.redis 穿透无中生有key&#xff0c;布隆过滤nul隔离 锁与非期解难题。缓存击穿过期key&#xff0c; 雪崩大量过期key&#xff0c;过期时间要随机。 面试必考三兄弟&#xff0c;可用限流来保底。 1.1 Redis的使用场景 根据自己简历上的业务进行回答 缓存穿透、击穿、雪崩、双…

人工智能再次进化 善用AI提升营运效率

人工智能无疑为我们的生活带来不少便利&#xff0c;也为商界和社会发展作出了重大贡献。事实上&#xff0c;它的起源最早可以追溯到70年前&#xff0c;只可惜过往的 AI 技术尚未如现时般成熟&#xff0c;可以做到的事也远比现在少&#xff1b;直至近期的 AI 技术取得了重大突破…

Redis缓存预热方案详解:提升应用性能与用户体验

文章目录 引言1. 为什么需要缓存预热&#xff1f;2. 缓存预热的基本原理2.1 数据选择2.2 加载策略 3. Redis缓存预热方案设计3.1 方案概述3.2 数据选择3.3 加载策略3.4 实现方式 4. 测试与监控4.1 单元测试4.2 监控 5. 总结 引言 在现代Web应用中&#xff0c;缓存技术已经成为…

【并行计算】CUDA基础

cuda程序的后缀&#xff1a;.cu 编译&#xff1a;nvcc hello_world.cu 执行&#xff1a;./hello_world.cu 使用语言还是C。 1. 核函数 __global__ void add(int *a, int *b, int *c) {*c *a *b; }核函数只能访问GPU的内存。也就是显存。CPU的存储它是碰不到的。 并且核…