当前位置: 首页 > article >正文

【C++】set与map模拟实现

目录

一、关联式容器

二、set与map

(一)set

(二)map

三、底层原理

四、红黑树迭代器

(一)红黑树的begin() 与 end()

(二)红黑树迭代器的++

(三)红黑树迭代器的--

五、红黑树迭代器的封装

(一)set的迭代器

(二)map的迭代器

六、set与map的模拟实现

(一)RBTree

(二)Set

(三)Map


一、关联式容器

        vector、list、deque等,这些容器统称为序列式容器,因为其底层为线性序列的数据结构,里面存储的是元素本身。

        而关联式容器也是用来存储数据的,与序列式容器不同的是,其里面存储的是<key, value>结构的键值对,在数据检索时比序列式容器效率更高。 

        关联式容器有两种,一种是map、set、multimap、multiset采用树形结构,他们的底层都是红黑树,另一种是哈希结构。

        SGI-STL中关于键值对的定义:

template <class T1, class T2>
struct pair
{
typedef T1 first_type;
typedef T2 second_type;
T1 first;
T2 second;
pair(): first(T1()), second(T2())
{}
pair(const T1& a, const T2& b): first(a), second(b)
{}
};

二、set与map

(一)set

           1、set是关联式容器,它表面上只存放value,实际底层中存放的是由<value,value>组成的键值对。

        2、set调用find将采用中序遍历,可以用于排序+去重。

        3、为了保证元素的唯一性,set中的元素均为const,所以并不能对元素进行修改,但可以进行插入删除。

        使用示例:

#include <set>
void TestSet()
{
    // 用数组array中的元素构造set
    int array[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0, 1, 3, 5, 7, 9, 2, 4, 
6, 8, 0 };
 set<int> s(array, array+sizeof(array)/sizeof(array));
 cout << s.size() << endl;
 // 正向打印set中的元素,从打印结果中可以看出:set可去重
 for (auto& e : s)
 cout << e << " ";
 cout << endl;
 // 使用迭代器逆向打印set中的元素
 for (auto it = s.rbegin(); it != s.rend(); ++it)
 cout << *it << " ";
 cout << endl;
 // set中值为3的元素出现了几次
 cout << s.count(3) << endl;
}

(二)map

        1. map是关联容器,它按照特定的次序(按照key来比较)存储由键值key和值value组合而成的元素。

        2. 在map中,键值key通常用于排序和唯一地标识元素,而值value中存储与此键值key关联的 内容。键值key和值value的类型可能不同,并且在map的内部,key与value通过成员类型 value_type绑定在一起,为其取别名称为pair:

        3. 在内部,map中的元素总是按照键值key进行比较排序的。

        4.map支持下标访问符,即在[]中放入key,就可以找到与key对应的value。 

#include <string>
#include <map>
void TestMap()
{
 map<string, string> m;
 // 向map中插入元素的方式:
 // 将键值对<"peach","桃子">插入map中,用pair直接来构造键值对
 m.insert(pair<string, string>("peach", "桃子"));
 // 将键值对<"peach","桃子">插入map中,用make_pair函数来构造键值对
 m.insert(make_pair("banan", "香蕉"));
 // 将<"apple", "">插入map中,插入成功,返回value的引用,将“苹果”赋值给该引
 用结果,
 m["apple"] = "苹果";
 cout << m.size() << endl;
 // 用迭代器去遍历map中的元素,可以得到一个按照key排序的序列
 for (auto& e : m)
 cout << e.first << "--->" << e.second << endl;
 cout << endl;
 // map中的键值对key一定是唯一的,如果key存在将插入失败
 auto ret = m.insert(make_pair("peach", "桃色"));
 if (ret.second)
 cout << "<peach, 桃色>不在map中, 已经插入" << endl;
 else
 cout << "键值为peach的元素已经存在:" << ret.first->first << "--->"
<< ret.first->second <<" 插入失败"<< endl;
 // 删除key为"apple"的元素
 m.erase("apple");
 if (1 == m.count("apple"))
 cout << "apple还在" << endl;
 else
 cout << "apple被吃了" << endl;
}

三、底层原理

        红黑树详见:【数据结构】红黑树-CSDN博客

        set 和 map 都是底层使用红黑树实现的,将红黑树封装为目标容器。巧妙的是,底层红黑树代码只有一份,但可以通过传入不同的模板参数分别实现set 与 map。

        通过上图我们可以得到,set是在传入参数给红黑树是模板为<key, key>的形式,而map是在传入参数给红黑树是模板为<key, pair<key, val>>的形式。红黑树在接收参数时,接收set时节点存入的是key形式的数据,而接收map时节点存入的是pair<key, val>形式的数据。因此,set和map中底层虽然都是红黑树,但这两种数据结构中的红黑树实例化类型并不相同

        这里就出现了一个问题,也就是红黑树底层并不知道数据内存的是单个key值,还是一个<key, val>键值对,那么如果使用该数据呢(例如在插入节点时需要按照key值大小来寻找插入位置)。实际上是可以通过仿函数来实现以上问题。

template <class K>
class set
{
    struct SetKeyOfT
    {
        const K& operator()(const K& key)//传入节点的值
        {
            return key;//返回key
        }
    };
private:
    RBTree<K, K, SetKeyOfT> _t;
};
class map
{
    struct MapKeyOfT
    {
        const K& operator()(const pair<K, V>& kv)//传入节点的值
        {
            return kv.first;//返回kv.first
        }
    };
private:
    RBTree<const K, pair<K,V>, MapKeyOfT> _t;
};
//利用模板确定传入对象是set还是map
template <class K, class T,class KeyOfT>
class RBTree//红黑树
{};

        本文通过增加传入模板参数将该仿函数传入给底层的红黑树,红黑树通过调用该仿函数来取得数据中的key值。

四、红黑树迭代器

        STL库中红黑树底层实际是有个头节点,而本文采用的是无头结点的常规红黑树仿写红黑树的迭代器。下面是红黑树中迭代器的封装。

typedef RBTreeIterator<V, V&, V*> iterator;
typedef RBTreeIterator<V, const V&, const V*> const_iterator;

(一)红黑树的begin() 与 end()

        本次将 begin() 返回最左下节点生成的迭代器,而 end() 返回值为 nullptr 的迭代器。

(二)红黑树迭代器的++

        针对于set 与 map 的迭代器的++,是变为中序遍历中该节点的后继节点。

        因此针对红黑树的++也得满足上述条件:

        1、如果当前节点的右孩子不为空,则迭代器++至该右子树中最小的的节点;

        2、如果当前节点的右孩子为空,迭代器++至子树为上层节点左子树的祖先节点。

Self& operator++()
{
    //右子树不为空
	if (_node->_right)
	{
		Node* min = _node->_right;
		while (min->_left)
		{
			min = min->_left;
		}
		_node = min;
	}
    //右子树为空
	else
	{
		Node* cur = _node;
		Node* parent = _node->_parent;
		while (parent && parent->_right == cur)
		{
			cur = parent;
			parent = parent->_parent;
		}
		_node = parent;
	}
	return *this;
}

(三)红黑树迭代器的--

        针对于set 与 map 的迭代器的--,是变为中序遍历中该节点的前驱节点。

        因此针对红黑树的--也得满足上述条件:

        1、如果当前节点的左孩子不为空,则迭代器--至该左子树中最大的的节点;

        2、如果当前节点的左孩子为空,迭代器--至子树为上层节点右子树的祖先节点。

Self& operator--()
{
    //左子树不为空
	if (_node->_left)
	{
		Node* max = _node->_left;
		while (max->_right)
		{
			max = max->_right;
		}
		_node = max;
	}
    //左子树为空
	else
	{
		Node* cur = _node;
		Node* parent = _node->_parent;
		while (parent && parent->_left == cur)
		{
			cur = parent;
			parent = parent->_parent;
		}
		_node = parent;
	}
	return *this;
}

五、红黑树迭代器的封装

(一)set的迭代器

        对于set,它的key都是不能改的。也就是无论是普通迭代器还是const 迭代器,我们统一使用红黑树的const迭代器进行封装。

typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;
typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;
iterator begin()const
{
    return _t.begin();
}
iterator end()const
{
    return _t.end();
}

        针对于上述代码,底层红黑树也必须提供对于const迭代器的 begin() 与 end()。

(二)map的迭代器

        对于map,它的key同样不能修改,但是其value是可以修改的。

typedef typename RBTree<const K, pair<const K, V>, MapKeyOfT>::iterator iterator;
typedef typename RBTree<const K, pair<const K, V>, MapKeyOfT>::const_iterator 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();
}

        因此封装红黑树迭代器的时候,无论普通迭代器还是const迭代器都需要在pair键值对中对key加上const修饰。

六、set与map的模拟实现

(一)RBTree

#pragma once
#pragma once
#include <iostream>
#include <cassert>
using namespace std;
enum Color
{
	red, black
};
template<class T>
struct RBTreeNode
{
	T _data;
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;
	Color _color;
	RBTreeNode(const T& data)
		:_data(data)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _color(red)
	{}
};

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

	RBTreeIterator(Node* node)
		:_node(node)
	{}
	RBTreeIterator(const iterator& it)
		:_node(it._node)
	{}
	// 请完善迭代器的++操作,让迭代器可以移动
	Self& operator++()
	{
		if (_node->_right)
		{
			Node* min = _node->_right;
			while (min->_left)
			{
				min = min->_left;
			}
			_node = min;
		}
		else
		{
			Node* cur = _node;
			Node* parent = _node->_parent;
			while (parent && parent->_right == cur)
			{
				cur = parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	}
	Self& operator--()
	{
		if (_node->_left)
		{
			Node* max = _node->_left;
			while (max->_right)
			{
				max = max->_right;
			}
			_node = max;
		}
		else
		{
			Node* cur = _node;
			Node* parent = _node->_parent;
			while (parent && parent->_left == cur)
			{
				cur = parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	}
	// 请完善下面两个操作,让迭代器可以像指针一样操作
	Ref operator*()
	{
		return _node->_data;
	}
	Ptr operator->()
	{
		return &(_node->_data);
	}

	// 请完善下面两个操作,让迭代器能够支持比较
	bool operator!=(const Self& s)const
	{
		return _node != s._node;
	}
	bool operator==(const Self& s)const
	{
		return _node == s._node;
	}
};
template<class K, class V, class KeyOfVal>
class RBTree
{
public:
	typedef RBTreeNode<V> Node;
	typedef RBTreeIterator<V, V&, V*> iterator;
	typedef RBTreeIterator<V, const V&, const V*> const_iterator;
	RBTree()
		:_root(nullptr)
	{}
	iterator begin()
	{
		Node* left = _root;
		while (left && left->_left)
		{
			left = left->_left;
		}
		return iterator(left);
	}
	iterator end()
	{
		return iterator(nullptr);
	}
	const_iterator begin() const
	{
		Node* left = _root;
		while (left && left->_left)
		{
			left = left->_left;
		}
		return const_iterator(left);
	}
	const_iterator end() const
	{
		return const_iterator(nullptr);
	}
	//中序遍历
	void Inorder()
	{
		_Inorder(_root);
	}
	// 左单旋
	void RotateL(Node* pParent)
	{
		Node* parent = pParent;
		Node* pNode = parent->_parent;
		Node* subR = parent->_right;

		parent->_right = subR->_left;
		if (subR->_left)
			subR->_left->_parent = parent;

		subR->_left = parent;
		parent->_parent = subR;

		if (pNode == nullptr)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (pNode->_left == parent)
			{
				pNode->_left = subR;
			}
			else
			{
				pNode->_right = subR;
			}
			subR->_parent = pNode;
		}
	}
	// 右单旋
	void RotateR(Node* pParent)
	{
		Node* parent = pParent;
		Node* pNode = parent->_parent;
		Node* subL = parent->_left;

		parent->_left = subL->_right;
		if (subL->_right)
			subL->_right->_parent = parent;

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

		if (pNode == nullptr)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			if (pNode->_left == parent)
			{
				pNode->_left = subL;
			}
			else
			{
				pNode->_right = subL;
			}
			subL->_parent = pNode;
		}
	}
	//插入节点
	pair<iterator, bool> Insert(const V& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_color = black;
			return make_pair(iterator(_root), true);
		}

		Node* parent = nullptr;
		Node* cur = _root;
		KeyOfVal kov;
		while (cur)
		{
			if (kov(cur->_data) < kov(data))
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (kov(cur->_data) > kov(data))
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return make_pair(iterator(cur), false);
			}
		}

		cur = new Node(data);
		Node* ret = cur;
		if (kov(parent->_data) < kov(data))
			parent->_right = cur;
		else
			parent->_left = cur;
		cur->_parent = parent;

		while (parent && parent->_color == red)
		{
			Node* grandparent = parent->_parent;
			Node* uncle = nullptr;
			if (grandparent == nullptr)
				break;
			if (parent == grandparent->_left)
				uncle = grandparent->_right;
			else
				uncle = grandparent->_left;

			if (uncle && uncle->_color == red)
			{
				parent->_color = uncle->_color = black;
				grandparent->_color = red;
				cur = grandparent;
				parent = cur->_parent;
			}
			else
			{
				if (grandparent->_right == parent && parent->_right == cur)
				{
					RotateL(grandparent);
					parent->_color = black;
					grandparent->_color = red;

				}
				else if (grandparent->_left == parent && parent->_left == cur)
				{
					RotateR(grandparent);
					parent->_color = black;
					grandparent->_color = red;
				}
				else if (grandparent->_right == parent && parent->_left == cur)
				{
					RotateR(parent);
					RotateL(grandparent);
					cur->_color = black;
					grandparent->_color = red;
				}
				else if (grandparent->_left == parent && parent->_right == cur)
				{
					RotateL(parent);
					RotateR(grandparent);
					cur->_color = black;
					grandparent->_color = red;
				}
				else
					assert(false);
				break;
			}
			_root->_color = black;
		}
		return make_pair(iterator(ret), true);
	}
	//判断是否为红黑树
	bool isRBTree()
	{
		if (_root->_color == red)
			return false;

		Node* cur = _root;
		int ref = 0;
		while (cur)
		{
			if (cur->_color == black)
				++ref;
			cur = cur->_left;
		}
		return check(_root, 0, ref);
	}
private:
	bool check(Node* t, int num, int ref)
	{
		if (t == nullptr)
		{
			if (num != ref)
			{
				cout << "黑色节点数目异常" << endl;
				return false;
			}
			return true;
		}

		if (t->_color == red && t->_parent->_color == red)
		{
			cout << "出现连续红色节点" << endl;
			return false;
		}
		if (t->_color == black)
			num++;
		return check(t->_left, num, ref) && check(t->_right, num, ref);
	}
	//中序遍历
	void _Inorder(Node* root)
	{
		if (root == nullptr)
			return;
		_Inorder(root->_left);
		cout << KeyOfVal()(root->_data) << " ";
		_Inorder(root->_right);
	}
	Node* _root;
};

(二)Set

#pragma once
#include "RBTree.h"
namespace mh
{
	template<class K>
	class set
	{
		struct KeyOfVal
		{
			const K& operator()(const K& data)
			{
				return data;
			}
		};
		typedef typename RBTree<K, K, KeyOfVal>::const_iterator iterator;
		typedef typename RBTree<K, const K, KeyOfVal>::const_iterator const_iterator;
	public:
		iterator begin() const
		{
			return _t.begin();
		}
		iterator end() const
		{
			return _t.end();
		}
		pair<iterator, bool> insert(const K& key)
		{
			pair<typename RBTree<K, K, KeyOfVal>::iterator, bool> ret = _t.Insert(key);
			return pair<iterator, bool>(ret.first, ret.second);
		}
	private:
		RBTree<K, K, KeyOfVal> _t;
	};
}

(三)Map

#pragma once
#include "RBTree.h"
namespace mh
{
	template<class K, class V>
	class map
	{
		struct KeyOfVal
		{
			const K& operator()(const pair<K, V>&data)
			{
				return data.first;
			}
		};
	public:
		typedef typename RBTree<K, pair<const K, V>, KeyOfVal>::iterator iterator;
		typedef typename RBTree<K, pair<const K, V>, KeyOfVal>::const_iterator const_iterator;

		iterator begin()
		{
			return _t.begin();
		}
		iterator end()
		{
			return _t.end();
		}
		const_iterator begin() const
		{
			return _t.begin();
		}
		const_iterator end() const
		{
			return end();
		}
		pair<iterator, bool> insert(const pair<K, V>& data)
		{
			return _t.Insert(data);
		}
		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>, KeyOfVal> _t;
	};
}

http://www.kler.cn/a/466083.html

相关文章:

  • 【网络安全 | 漏洞挖掘】JS Review + GraphQL滥用实现管理面板访问
  • Flask 快速入门
  • 047_小驰私房菜_Qcom 8系列,Jpeg GPU 旋转
  • 【踩坑指南:2025年最新】如何在Linux(Ubuntu)启动第一个Scala Hello World程序(Scala3)
  • 【2025最新计算机毕业设计】基于Spring Boot+Vue影院购票系统(高质量源码,提供文档,免费部署到本地)
  • Elasticsearch:减少 Elastic 容器镜像中的 CVE(常见的漏洞和暴露)
  • 数据可视化搭配数据分析,解锁数据潜能的密码
  • 利用大语言模型解决推理任务
  • Springboot - Web
  • C++STL中bitset的介绍与使用
  • 数据库软考历年上午真题与答案解析(2018-2024)
  • 点击<el-dropdown>中某一项跳转页面时,控制台报错的问题
  • 基于海豚调度功能开发——推送下游系统数据库连接信息批量修改方案与实现
  • 算法-10进制转换成16进制,负数用补码表示
  • 一、二极管(模电理论篇)
  • ubuntu安装firefox
  • aardio —— 改变按钮文本颜色
  • Node.js应用程序遇到了内存溢出的问题
  • IP5385应用于移动电源快充方案的30W到100W大功率电源管理芯片
  • 服务器开发 的编程环境(programming environment)核心知识
  • Linux下部署ElasticSearch集群
  • 基于SpringBoot和Thymeleaf的仿小米电商系统源码下载与安装指南-幽络源
  • Win11+WLS Ubuntu 鸿蒙开发环境搭建(二)
  • DVWA靶场文件上传漏洞全级别通关及源码深度解析
  • 使用rknn进行yoloV8部署(C++)
  • 六种主流服务器的选择与使用