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

set和map的模拟

目录

1.介绍

2.模拟实现

2.1节点的改造

2.2迭代器的实现

2.2.1

2.2.2

3.完整代码


1.介绍

STL中的set和map的底层都是用红黑树模拟实现的。但set每次只存储一个值,而map存储的是键值对,那是否要写两份红黑树的代码呢?

可写看下之前写的博客:可怕的红黑树_"派派"的博客-CSDN博客,实现是与map相似的。

2.模拟实现

2.1节点的改造

我们可用模板对红黑树进行改造,让map和set同时适用。

在红黑树的代码中,我们可把存储每个节点的结构代码体更改为:

enum Colour
{
	RED,
	BLACK,
};

template<class T>
struct RBTreeNode
{
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;
	T _data; // 存储数据
	Colour _col;

	RBTreeNode(const T& data)
		:_data(data)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _col(RED)
	{}
};

其中根据T中的类型进行存储,例如:T可以是set传来的int类型等,map传来的键值对。

但在插入时需要比较数据的大小,T可能为int类型或则键值对。所以需要再增加一个仿函数,用来提取第一个参数(map和set都是用第一个参数比较大小的)。

set中:

 template<class K>
class Set
{
	struct SetKeyOfT
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};
public:
	bool insert(const K& key)
	{
		return _t.Insert(key);

	}

private:
	RBTree<K, K, SetKeyOfT> _t;
};

map中:

  template<class K, class V>
class Map
{
	struct MapKeyOfT
	{
		const K& operator()(const pair<K, V>& kv)//提取第一个参数
		{
			return kv.first;
		}
	};
public:
	bool insert(const pair<K,V>& kv)
	{
		_t.Insert(kv);

	}


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

红黑树的代码部分:

template<class K, class T, class KeyOfT>//keyOfT用来接收仿函数
class RBTree
{
	typedef RBTreeNode<T> Node;
public:
	bool Insert(const T& data)
	{

		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;

			return true;
		}

		KeyOfT kot;
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (kot(cur->_data) < kot(data))
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (kot(cur->_data) > kot(data))
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{

				return false;
			}
		}
		cur = new Node(data);
		Node* newnode = cur;
		cur->_col = RED;
		if (kot(parent->_data) < kot(data))
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;
       ..................
       ..................
       //旋转.....
}
}

画图理解下:

 我们可验证下结果是否准确,可在红黑树中定义一个中序遍历代码,然后在set,map中调用。

void test_set1()
{
	Set<int> s;
	s.insert(8);
	s.insert(6);
	s.insert(5);
	s.insert(7);
	s.insert(2);
	s.insert(9);
	Map<string, int>m;
	m.insert(make_pair("香蕉", 1));
	m.insert(make_pair("苹果", 1));
	m.insert(make_pair("西瓜", 1));
	m.insert(make_pair("草莓", 1));
	s.Inorder();
	cout << endl;
	m.Inorder();
}

 结果:

 

2.2迭代器的实现

红黑树也是可以像string,vector一样,通过迭代器像指针一样去遍历节点,不过它更像list的迭代器,不是一个原生指针,而是通过封装了节点的指针,对指针进行操作。

在红黑树中增加下面的代码:

    typedef __RBTreeIterator<T, T&, T*> iterator;
	typedef __RBTreeIterator<T, const T&, const T*> const_iterator;
	iterator Begin()//找其最左节点
	{
		Node* subLeft = _root;
		while (subLeft && subLeft->_left)
		{
			subLeft = subLeft->_left;
		}

		return iterator(subLeft);
	}

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

	const_iterator Begin() const
	{
		Node* subLeft = _root;
		while (subLeft && subLeft->_left)
		{
			subLeft = subLeft->_left;
		}

		return const_iterator(subLeft);
	}

	const_iterator End() const
	{
		return const_iterator(nullptr);
	}

    iterator Find(const K& key)//查找
	{
		Node* cur = _root;
		KeyOfT kot;
		while (cur)
		{
			if (kot(cur->_data) < key)
			{
				cur = cur->_right;
			}
			else if (kot(cur->_data) > key)
			{
				cur = cur->_left;
			}
			else
			{
				return iterator(cur);
			}
		}

		return End();
	}

迭代器的实现:

2.2.1

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

	__RBTreeIterator(Node* node)
		:_node(node)
	{}

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

	Ptr operator->()
	{
		return &_node->_data;
	}
	
	Self& operator++()
	{
      ............
      ............
	}

	Self operator++(int)
	{
		Self tmp(*this);
		
		++(*this);

		return tmp;
	}

	Self& operator--()
	{
		..........
        ..........
	}

	Self operator--(int)
	{
		Self tmp(*this);

		--(*this);

		return tmp;
	}

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

	bool operator==(const Self& s) const
	{
		return _node == s->_node;
	}
};

2.2.2

++的实现:

++的实现与中序遍历的规则类似,但每次只走一步。

思路:当一个节点有右子树时,对其节点++,之后指针是指向该右子树的最左节点。

           当一个节点无右子树时,对其节点++,之后指针是指向(在其祖先里,孩子是属于祖先左              孩子的)

如图:

    Self& operator++()
	{
		if (_node->_right == nullptr)
		{
			// 找祖先里面,孩子是父亲左的那个
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && parent->_right == cur)
			{
				cur = cur->_parent;
				parent = parent->_parent;
			}

			_node = parent;
		}
		else
		{
			// 右子树的最左节点
			Node* subLeft = _node->_right;
			while (subLeft->_left)
			{
				subLeft = subLeft->_left;
			}

			_node = subLeft;
		}
		return *this;
	}

--的实现:

思路:当左子树不为空时,对其节点--,指向的是该左子树的最右节点。

           当左子树为空时,对其节点--,之后指针是指向(在其祖先里,孩子是属于祖先右                           孩子的)。

如图:

Self& operator--()
	{
		if (_node->_left == nullptr)
		{
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && cur == parent->_left)
			{
				cur = cur->_parent;
				parent = parent->_parent;
			}

			_node = parent;
		}
		else
		{
			// 左子树的最右节点
			Node* subRight = _node->_left;
			while (subRight->_right)
			{
				subRight = subRight->_right;
			}
			_node = subRight;
		}

		return *this;
	}

当迭代器实现后,红黑树的插入代码要进行更改,insert函数返回值是pair,第一个参数是指向当前节点的迭代器,第二个参数是true/false;(插入成功返回true,否则返回false)。

3.完整代码

set:

template<class K>
	class Set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		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();
		}

		pair<iterator, bool> insert(const K& key)
		{
			//pair<typename RBTree<K, K, SetKeyOfT>::iterator, bool> ret = 
            //                                        _t.Insert(key);
			auto ret = _t.Insert(key);
			return pair<iterator, bool>(iterator(ret.first._node), ret.second);
		}

		iterator find(const K& key)
		{
			return _t.Find(key);
		}
	private:
		RBTree<K, K, SetKeyOfT> _t;
	};

map代码:

   template<class K, class V>
	class Map
	{
		struct MapKeyOfT
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;
			}
		};
	public:
		typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::iterator iterator;
		typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::const_iterator const_iterator;

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

		iterator end()
		{
			return _t.End();
		}

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

		iterator find(const K& key)
		{
			return _t.Find(key);
		}

		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = insert(make_pair(key, V()));
			return ret.first->second;
		}

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

相关文章:

  • window环境下安装大数据环境
  • 解决navicat premium连接数据库自动断开问题
  • 学历提升中的我,入职产品经理之路
  • 网络安全专家,这5本入门秘籍人手一套
  • 智源AI日报(2022-08-30): 华为谢凌曦:关于视觉识别领域发展的个人观点
  • 示波器十大基础知识你都了解多少
  • 【经典算法学习-排序篇】冒泡排序
  • Nacos系列【26】源码分析篇之客户端自动注册
  • DBeaver常用快捷键(含复制当前行)
  • Java ThreadPoolExecutor的拒绝策略
  • 操作系统——磁盘操作
  • DSPE-PEG-FSHB,FSHB-PEG-DSPE,磷脂-聚乙二醇-靶向多肽FSHB
  • JAVA 力扣练习题:回文数
  • 【Git】credential.helper
  • PDF格式分析(六十九)——注释字典
  • 【每日笔记】【Go学习笔记】2019-01-10 codis proxy处理流程
  • Android系统模拟器绘制实现概述
  • conda常用的命令
  • Hexo+码云+git快速搭建免费的静态Blog
  • nfs客户端进程变D,延伸linux的lock
  • nginx 配置多 域名 + 多 https
  • Python 基础起步 (十) 什么叫函数?
  • RxJS: 简单入门
  • SOFAMosn配置模型
  • 初识MongoDB分片
  • 分布式任务队列Celery
  • 优秀架构师必须掌握的架构思维
  • 在GitHub多个账号上使用不同的SSH的配置方法
  • 专访Pony.ai 楼天城:自动驾驶已经走过了“从0到1”,“规模”是行业的分水岭| 自动驾驶这十年 ...
  • ​​​​​​​​​​​​​​Γ函数
  • ​LeetCode解法汇总1410. HTML 实体解析器
  • ​软考-高级-系统架构设计师教程(清华第2版)【第1章-绪论-思维导图】​
  • $$$$GB2312-80区位编码表$$$$
  • (1)Android开发优化---------UI优化
  • (带教程)商业版SEO关键词按天计费系统:关键词排名优化、代理服务、手机自适应及搭建教程
  • (定时器/计数器)中断系统(详解与使用)
  • (附源码)node.js知识分享网站 毕业设计 202038
  • (一)Java算法:二分查找
  • (转)jdk与jre的区别
  • (转)Linux NTP配置详解 (Network Time Protocol)
  • (转)Oracle 9i 数据库设计指引全集(1)
  • (转)清华学霸演讲稿:永远不要说你已经尽力了
  • (转载)OpenStack Hacker养成指南
  • 、写入Shellcode到注册表上线
  • .NET Framework 4.6.2改进了WPF和安全性
  • .net Signalr 使用笔记
  • .NET高级面试指南专题十一【 设计模式介绍,为什么要用设计模式】
  • .NET和.COM和.CN域名区别
  • .NET应用架构设计:原则、模式与实践 目录预览
  • @Autowired多个相同类型bean装配问题
  • [android] 切换界面的通用处理
  • [AR]Vumark(下一代条形码)
  • [Assignment] C++1
  • [cogs2652]秘术「天文密葬法」
  • [EFI]英特尔 冥王峡谷 NUC8i7HVK 电脑 Hackintosh 黑苹果efi引导文件