C++:map和set的封装原理

news2024/11/27 5:31:06

文章目录

  • 红黑树的封装
  • map和set的封装
  • 红黑树迭代器的实现
    • operator ++ 和 -- 的实现
      • ++的实现过程
    • 迭代器的其他模块
  • 整体实现

本篇写于红黑树模拟实现后,对map和set进行封装,模拟实现map和set内部的原理

首先,map和set的底层逻辑是红黑树,那么就意味着不再需要像vector/list这样重新进行写入,而是直接在红黑树的基础上进行适当的封装即可

但是,前面实现的红黑树并不能完美的适配所需要的功能,因此要在一定程度上对红黑树进行改造后,才能很好的使用,那么下面首先要对红黑树进行一定程度的改造

本篇将分析STL库中对红黑树的封装原理,搭建出基础的框架,并进行分析

红黑树的封装

拿出一份关于STL中的源码,分析源码中是如何对这这棵树进行封装的

在红黑树的定义中就不太一样,在前面实现的过程中,是直接将pair键值对当做模板中的参数的,导致只有两种,一种是Key/Key模型,一种是Key/Value模型,而在源码的实现中,则是直接将类型放到模板中来实现,用泛型的思想编程
在这里插入图片描述
在map和set中,传参直接将需要传递的参数传到Value处:

在这里插入图片描述
因此在封装的时候,就可以这样进行封装,先对我们自己完成的红黑树进行一些改善

首先,对节点进行改善

template<class T>
struct RBTreeNode
{
	RBTreeNode(const T& data)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _data(data)
		, _col(RED)
	{}

	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;
	T _data;
	Color _col;
};

map和set的封装

下面可以对map和set进行初步封装了

namespace mymap
{
	template<class K, class V>
	class set
	{
	private:
		RBTree<K, pair<K, V>> _t;
	};
}


namespace myset
{
	template<class K>
	class set
	{
	private:
		RBTree<K, K> _t;
	};
}

下一个遇到的问题是,在插入数据的时候如何比较大小呢?例如下面的场景

在这里插入图片描述
上面是在模拟实现红黑树的过程中实现的逻辑,现在问题是,如何对于键值对进行比较?

在库函数中寻找对策:

在这里插入图片描述
比较的规则是比较键值对的第一个元素,因此现在需要做的就是想办法取出来键值对的第一个元素进行比较,因此就可以采取这样的思想,在map和set中都搞一个能把数据取出来的仿函数,在进行比较的时候返回的是Key即可,因此要修改红黑树的模板参数,接收一个仿函数的值,并用其来参与插入数据时的比较

namespace mymap
{
	template<class K, class V>
	class map
	{
	private:
		struct MapKeyOfT
		{
			const K& operator()(const V& data)
			{
				// 对于map来讲,Key是传入的data键值对中的第一个元素
				return data.first;
			}
		};
		RBTree<K, pair<K, V>, MapKeyOfT> _t;
	};
}

namespace myset
{
	template<class K>
	class set
	{
	public:
		struct SetKeyOfT
		{
			const K& operator()(const K& data)
			{
				// 对于set来讲,K就是data本身
				return data.first;
			}
		};
	private:
		RBTree<K, K, SetKeyOfT> _t;
	};
}

此时基本逻辑就已经搭建起来了,在map/set中进行插入元素,实际上就是插入到树中,在外部再对这个插入的过程进行一次封装

在这里插入图片描述

pair<iterator,bool> insert(const V& val)
{
	return _t.insert(val);
}

红黑树迭代器的实现

于是,要实现出红黑树的迭代器,红黑树的迭代器和链表的迭代器类似,都要借助一个类来构建

template<class T,class Ptr,class Ref>
struct _TreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef _TreeIterator<T, T*, T&> Self;

	// 构造函数:迭代器的本质就是封装了一层指针
	_TreeIterator(Node* node)
		:_node(node)
	{}

	// 迭代器中功能的实现
	// 解引用取数据
	Ref operator*()
	{
		return _node->_data;
	}

	// 取数据的地址
	Ptr operator->()
	{
		return &(_node->_data);
	}

	//  实现++



	// 迭代器的本质是节点的指针
	Node* _node;
};

operator ++ 和 – 的实现

++的实现过程

在这里插入图片描述

以上图为例,此时指向的是17,从正常来说遍历次序是按照中序遍历,因此比17大的下一个数是22,总结出结论就是,++要寻找的是右孩子的最左子树,如果没有右子树如何处理?

在这里插入图片描述

如果没有右子树,就说明此时已经到了遍历结束了左子树,如上图所示,此时要找的是,孩子是父亲左的那个节点的祖先

根据上述逻辑,完善迭代器的代码

	//  实现++
	Self& operator++()
	{
		//  如果右子树存在,就到右子树中找最左节点
		if (_node->_right)
		{
			// 下一个就是右子树的最左节点
			Node* cur = _node->_right;
			while (cur->_left)
			{
				cur = cur->_left;
			}

			_node = cur;
		}
		// 如果右子树不存在,就到孩子是父亲左的那个祖先
		else
		{
			// 左子树 根 右子树
			Node* cur = _node;
			Node* parent = cur->_parent;
			// 只要孩子是父亲的右,就继续向上循环找,直到找到孩子是父亲左的那个节点
			while (parent && cur == parent->_right)
			{
				cur = parent;
				parent = parent->_parent;
			}

			_node = parent;
		}

		return *this;
	}

	// 实现--
	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 = parent->_parent;
			}

			_node = parent;
		}

		return *this;
	}

迭代器的其他模块

	// 判断相等和不相等,实际上就是看迭代器内部封装的节点是否是同一个节点即可
	bool operator==(const Self& it)
	{
		return _node == it._node;
	}

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

整体实现

改造过后的红黑树

#pragma once
#include <iostream>
using namespace std;

enum Color
{
	RED,
	BLACK
};

template<class T>
struct RBTreeNode
{
	RBTreeNode(const T& data)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _data(data)
		, _col(RED)
	{}

	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;
	T _data;
	Color _col;
};

template<class T, class Ptr, class Ref>
struct _TreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef _TreeIterator<T, T*, T&> Self;

	// 构造函数:迭代器的本质就是封装了一层指针
	_TreeIterator(Node* node)
		:_node(node)
	{}

	// 迭代器中功能的实现
	// 解引用取数据
	Ref operator*()
	{
		return _node->_data;
	}

	// 取数据的地址
	Ptr operator->()
	{
		return &(_node->_data);
	}

	//  实现++
	Self& operator++()
	{
		//  如果右子树存在,就到右子树中找最左节点
		if (_node->_right)
		{
			// 下一个就是右子树的最左节点
			Node* cur = _node->_right;
			while (cur->_left)
			{
				cur = cur->_left;
			}

			_node = cur;
		}
		// 如果右子树不存在,就到孩子是父亲左的那个祖先
		else
		{
			// 左子树 根 右子树
			Node* cur = _node;
			Node* parent = cur->_parent;
			// 只要孩子是父亲的右,就继续向上循环找,直到找到孩子是父亲左的那个节点
			while (parent && cur == parent->_right)
			{
				cur = parent;
				parent = parent->_parent;
			}

			_node = parent;
		}

		return *this;
	}

	// 实现--
	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 = parent->_parent;
			}

			_node = parent;
		}

		return *this;
	}

	// 判断相等和不相等,实际上就是看迭代器内部封装的节点是否是同一个节点即可
	bool operator==(const Self& it)
	{
		return _node == it._node;
	}

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

	// 迭代器的本质是节点的指针
	Node* _node;
};

template<class K, class T, class KeyOfT>
class RBTree
{
public:
	typedef RBTreeNode<T> Node;
	typedef _TreeIterator<T, T*, T&> iterator;
	//  红黑树的构造函数
	RBTree()
		:_root(nullptr)
	{}

	// 红黑树的析构函数
	~RBTree()
	{
		DelNode(_root);
	}

	// 红黑树迭代器部分
	iterator begin()
	{
		Node* cur = _root;
		while (cur && cur->_left)
		{
			cur = cur->_left;
		}
		return iterator(cur);
	}

	iterator end()
	{
		return iterator(nullptr);
	}

	// 元素的插入
	pair<iterator, bool> insert(const T& data)
	{
		Node* cur = _root;
		Node* parent = nullptr;
		KeyOfT kot;
		// 根据搜索二叉树的基本逻辑完成
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return make_pair(iterator(_root), true);
		}
		else
		{
			// 插入数据
			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;
				}
				else
				{
					return make_pair(iterator(cur), false);
				}
			}
			// 此时parent指向最后一个节点,cur为空
			cur = new Node(data);
			if (kot(parent->_data) > kot(cur->_data))
			{
				// 如果插入节点小于它的父亲,就插入到左边
				parent->_left = cur;
				cur->_parent = parent;
			}
			else
			{
				// 如果插入节点大于它的父亲,就插入到右边
				parent->_right = cur;
				cur->_parent = parent;
			}
		}

		// 至此,普通二叉搜索树的插入已经完成,该进行红黑树的高度调整了
		// 终止条件是parent为空,或者parent已经是黑色了,就意味着不需要调整了
		// parent是红色,意味着grandparent一定存在
		while (parent && parent->_col == RED)
		{
			// 更变的核心是舅舅,因此要先找到舅舅
			// 整体来说还有两种情况,parent可能是grandparent的左或者右,舅舅就是另外一边
			Node* grandparent = parent->_parent;
			if (parent == grandparent->_left)
			{
				Node* uncle = grandparent->_right;
				// 1. 舅舅存在,并且是红色
				if (uncle && uncle->_col == RED)
				{
					//     g
					//   p   u
					// c

					// 变色
					parent->_col = uncle->_col = BLACK;
					grandparent->_col = RED;

					// 向上处理
					cur = grandparent;
					parent = cur->_parent;
				}

				// 2. 舅舅不存在
				else
				{
					// 如果cur是左孩子
					if (cur == parent->_left)
					{
						//     g
						//   p
						// c

						// 对grandparent进行右旋
						RotateR(grandparent);
						// 变色
						cur->_col = grandparent->_col = RED;
						parent->_col = BLACK;
					}
					// 如果cur是右孩子
					else
					{
						//     g               g
						//  p       -->     c         -->    c
						//    c           p                p   g

						// 对parent左旋,对grandparent右旋
						RotateL(parent);
						RotateR(grandparent);
						// 变色
						cur->_col = BLACK;
						parent->_col = grandparent->_col = RED;
					}

					// 更新之后parent和grandparent顺序乱了,而且也不需要继续调整了,直接break
					break;
				}
			}

			// parent是grandparent的右孩子,相同的逻辑再进行一次
			else
			{
				Node* uncle = grandparent->_left;
				if (uncle && uncle->_col == RED)
				{
					// 变色
					parent->_col = uncle->_col = BLACK;
					grandparent->_col = RED;

					// 继续往上处理
					cur = grandparent;
					parent = cur->_parent;
				}
				else
				{
					if (cur == parent->_right)
					{
						//   g
						//      p
						//         c
						RotateL(grandparent);
						parent->_col = BLACK;
						grandparent->_col = RED;
					}
					else
					{
						//     g
						//       p 
						//     c

						RotateR(parent);
						RotateL(grandparent);
						cur->_col = BLACK;
						grandparent->_col = RED;
					}

					break;
				}
			}
		}
		// 不管上面怎么变都无所谓,只需要保证根节点是黑的就可以了
		_root->_col = BLACK;

		return make_pair(iterator(cur), true);
	}

private:
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		parent->_right = subRL;
		subR->_left = parent;

		Node* parentParent = parent->_parent;

		parent->_parent = subR;
		if (subRL)
			subRL->_parent = parent;

		if (_root == parent)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (parentParent->_left == parent)
			{
				parentParent->_left = subR;
			}
			else
			{
				parentParent->_right = subR;
			}

			subR->_parent = parentParent;
		}
	}

	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;

		Node* parentParent = parent->_parent;

		subL->_right = parent;
		parent->_parent = subL;

		if (_root == parent)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			if (parentParent->_left == parent)
			{
				parentParent->_left = subL;
			}
			else
			{
				parentParent->_right = subL;
			}

			subL->_parent = parentParent;
		}
	}

	void DelNode(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

		DelNode(root->_left);
		DelNode(root->_right);
		delete(root);
	}

	Node* _root = nullptr;
};

依据红黑树封装出的set

#pragma once

#include "RBTree.h"

namespace myset
{
	template<class K>
	class set
	{
	public:
		struct SetKeyOfT
		{
			const K& operator()(const K& data)
			{
				// 对于set来讲,K就是data本身
				return data;
			}
		};
		typedef typename RBTree<K, K, SetKeyOfT>::iterator iterator;

		pair<iterator, bool> insert(const K& val)
		{
			return _t.insert(val);
		}

		iterator begin()
		{
			return _t.begin();
		}

		iterator end()
		{
			return _t.end();
		}
	private:
		RBTree<K, K, SetKeyOfT> _t;
	};
}

依据红黑树构造出的map

#pragma once

#include "RBTree.h"

namespace mymap
{
	template<class K, class V>
	class map
	{
	public:
		struct MapKeyOfT
		{
			const K& operator()(const pair<K, V>& data)
			{
				// 对于map来讲,key是键值对的第一个元素
				return data.first;
			}
		};
		typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::iterator iterator;
		pair<iterator, bool> insert(const pair<K, V>& val)
		{
			return _t.insert(val);
		}

		iterator begin()
		{
			return _t.begin();
		}

		iterator end()
		{
			return _t.end();
		}
	private:

		RBTree<K, pair<K, V>, MapKeyOfT> _t;
	};
}

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

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

相关文章

【Apache Doris】审计日志插件 | 快速体验

【Apache Doris】审计日志插件 | 快速体验 一、 环境信息1.1 硬件信息1.2 软件信息 二、 审计日志插件介绍三、 快速 体验3.1 AuditLoader 配置3.1.1 下载 Audit Loader 插件3.1.2 解压安装包3.1.3 修改 plugin.conf 3.2 创建库表3.3 初始化3.4 验证 一、 环境信息 1.1 硬件信…

识别伪装IP的网络攻击方法

识别伪装IP的网络攻击可以通过以下几种方法&#xff1a; 观察IP地址的异常现象。攻击者在使用伪装IP地址进行攻击时&#xff0c;往往会存在一些异常现象&#xff0c;如突然出现的未知IP地址、异常的流量等。这些现象可能是攻击的痕迹&#xff0c;需要对此加以留意。 检查网络通…

私有云边界网络部署实践

业务背景 在私有云的业务场景中&#xff0c;常见的通信中包含了同VPC内虚机互访、不同VPC之间的虚机互访、VPC访问Underlay资源、VPC访问Internet资源、VPC提供服务&#xff0c;被Internet访问、VPC与专线网络之间互访等&#xff1b;实际应用中&#xff0c;大多数云业务通信场…

车间安灯呼叫看板的功能与优势介绍

现在的工厂车间的管理变得越来越复杂&#xff0c;生产过程中可能会出现各种问题&#xff0c;如设备故障、物料短缺、工人伤病等。为了提高生产效率、优化生产管理&#xff0c;许多工厂引入了车间安灯呼叫看板系统。本文将介绍车间安灯呼叫看板的功能与优势。 一、功能介绍 1. 实…

[单片机课程设计报告汇总] 单片机设计报告常用硬件元器件描述

[单片机课程设计必看] 单片机设计报告常用描述 硬件设计 AT89C51最小系统 AT89C51是美国ATMEL公司生产的低电压&#xff0c;高性能CMOS16位单片机&#xff0c;片内含4k bytes的可反复擦写的只读程序存储器和128 bytes的随机存取数据存储器&#xff0c;期间采用ATMEL公司的高…

python matlplotlib/seaborn 绘制曲线的平均值标准差阴影图

1. seaborn 旧版本(0.8.1)中使用tsplot&#xff0c;新版本中使用lineplot 直线代表均值&#xff0c;阴影代表meanstd&#xff08;带有置信区间&#xff0c;参数ci&#xff09; import seaborn as sns import matplotlib.pyplot as plt import numpy as np import pandas as p…

攻略 | 参与Moonbeam Ignite Ecosystem Tour

Moonbeam联合Moonwell和Beamswap一起举办社区链上活动&#xff0c;旨在让社区用户通过任务来探索Moonbeam、Moonwell、Beamswap平台。在了解如何使用的同时&#xff0c;参与任务挑战还有机会分得 1700 USDC 奖池 &#x1f381; 的奖励&#xff01;我已经完成全部任务&#xff0…

AR人脸道具SDK,打造极致用户体验

为了满足企业在AR领域的应用需求&#xff0c;美摄科技推出了一款领先的AR人脸道具SDK&#xff0c;旨在帮助企业快速、高效地开发出具有丰富玩法体验的AR应用&#xff0c;从而提升企业的竞争力和市场份额。 一、丰富的AR人脸道具&#xff0c;满足多样化需求 美摄科技AR人脸道具…

怎么阅读芯片源代码(rtl)

一个rtl可以是这样的&#xff1a; 经常大家习惯于算法和数据结构。对于设计的部分&#xff0c;落实不一定多。 另外一个rtl也可以是这样的&#xff1a; 所以从不同的层面来讲&#xff0c;一个Rtl有不同的表述。 首先大概把所有的部分浏览一遍&#xff0c;看看有些什么。 其次…

喜报不断!箱讯平台获评2023年上海市促进现代航运服务业创新示范项目

近期&#xff0c;可谓捷报频传&#xff01;在箱讯科技子公司苏州箱讯获评苏州市软件和信息服务业 “头雁”培育企业没过多久&#xff0c;就又迎来好消息&#xff01; 日前&#xff0c;上海市交通委发布“2023年上海市促进现代航运服务业创新项目”评选结果&#xff0c;箱讯An…

抖音小程序开发全攻略:如何规划项目和选择合适的开发团队

在数字化时代&#xff0c;抖音小程序成为企业推广和服务的重要渠道。本文将为您提供抖音小程序开发的全面攻略&#xff0c;重点介绍如何规划项目和选择合适的开发团队&#xff0c;并附有一些关键的技术代码示例。 1. 项目规划 在开始抖音小程序开发之前&#xff0c;详细的项…

能够导出源代码的低代码平台有哪些?

目录 一、源码的优势 &#xff08;1&#xff09;定制性需求&#xff1a; &#xff08;2&#xff09;适应未来需求变化&#xff1a; &#xff08;3&#xff09;安全和可靠性&#xff1a; &#xff08;4&#xff09;高级功能和集成&#xff1a; 二、支持源代码的厂商 目前国内大多…

Javaweb开发 利用servlet+jsp+jdbc+tomcat数据库实现登录功能

前言&#xff1a;很久没更新了&#xff0c;今天给大家分享一个Java web的小案例&#xff0c;是一个登录页面&#xff0c;利用Login控制类和JDBC连接数据库&#xff0c;并判断用户名密码是否正确&#xff0c;项目最终部署在Tomcat上。 先看效果 正文 一、前期工作 1.首先我们…

Netty+SpringBoot 打造一个 TCP 长连接通讯方案

项目背景 最近公司某物联网项目需要使用socket长连接进行消息通讯&#xff0c;捣鼓了一版代码上线&#xff0c;结果BUG不断&#xff0c;本猿寝食难安&#xff0c;于是求助度娘&#xff0c;数日未眠项目终于平稳运行了&#xff0c;本着开源共享的精神&#xff0c;本猿把项目代码…

Excel-快速将公式运用到一整列

先在该列的第一个单元格里写好公式&#xff0c;然后单击该单元格 在图中标示的地方输入我们需要填充的单元格区域 同时按住Ctrl和Enter键&#xff0c;这时需要填充的单元格区域就都被选中了 然后单击一下图中公式的后面&#xff0c;再次按下Ctrl和Enter键&#xff0c;这样就完…

今日最新版早安问候大全,创意好看的早上好祝福图片带字温馨

1、阳光照&#xff0c;鸟欢叫&#xff0c;小懒猪&#xff0c;起床了&#xff0c;伸懒腰&#xff0c;笑一笑&#xff0c;深呼吸&#xff0c;精神好&#xff0c;开心到&#xff0c;欢乐抱&#xff0c;幸福随&#xff0c;乐淘淘&#xff0c;好运伴&#xff0c;祝福来&#xff0c;每…

《线性代数》科教版教材必会习题

出一期比较尴尬的博客——有关线代教材的课后题总结~ 之所以说尴尬&#xff0c;主要有两个主要原因&#xff1a;这本科教版第三版的教材&#xff0c;整体看起来并不是那么舒服&#xff0c;甚至被我们的老师吐槽过&#xff0c;更好地选择时同济版的那本紫书——我们学校的新生这…

windows10上使用Visual Studio对树莓派进行交叉编译示例

本文主要介绍通过Visual Studio对树莓派进行交叉编译的方法。 1 环境 宿主机&#xff1a; 系统&#xff1a;Windows10 开发平台&#xff1a;Visual Studio 2022 (我用的是社区版) VisualGDB: VisualGDB - Download (我下的试用版本) GNU工具链: Prebuilt GNU toolchain f…

中国人民大学与加拿大女王大学金融硕士——热爱会穿越时间,埋在心底的读研梦也是

随着时光的流转&#xff0c;我们都在跌跌撞撞中成长&#xff0c;改变&#xff0c;但有一种东西是永恒的&#xff0c;那就是我们对梦想的渴望。那些被尘封的读研梦想&#xff0c;如同穿越漫长岁月&#xff0c;等待在未来的某一天重见天日。梦想&#xff0c;就如同热爱一样&#…