【C++练级之路】【Lv.17】【STL】set类和map类的模拟实现

这篇具有很好参考价值的文章主要介绍了【C++练级之路】【Lv.17】【STL】set类和map类的模拟实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


【C++练级之路】【Lv.17】【STL】set类和map类的模拟实现,进击的C++,c++,开发语言,stl,容器,数据结构

快乐的流畅:个人主页
个人专栏:《C语言》《数据结构世界》《进击的C++》
远方有一堆篝火,在为久候之人燃烧!

引言

STL库中的set类和map类,其底层原理都是通过红黑树来实现的。尽管set和map可以各自实现一棵红黑树,但是为了提高代码的复用率,STL库中将红黑树进行了一定的改造,实现以相同的底层实现不同的容器

一、红黑树(改造版)

1.1 结点

enum Color
{
	RED,
	BLACK
};

template<class T>
struct RBTreeNode
{
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;
	T _data;
	Color _col;

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

细节:

  • 数据类型改为T,因为要同时适用set(存储键值)和map(存储键值对)

1.2 迭代器

改造后的红黑树,最重要的功能之一就是支持双向迭代器,以最左结点为首,以最右结点为尾。

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

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

	RBTreeIterator(const Iterator& it)
		: _node(it._node)
	{}

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

	Ptr operator->()
	{
		return &(operator*());
	}

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

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

细节:

  1. 一些基本的迭代器范式操作已经给出,重点的++与- -操作后面详细实现
  2. 迭代器的拷贝构造函数有两个用途:
    • 以普通迭代器拷贝出普通迭代器(普通迭代器调用时)
    • 以普通迭代器拷贝出const迭代器(const迭代器调用时)

1.2.1 operator++

Self& operator++()
{
	if (_node->_right)//右不为空,找右子树的最左结点
	{
		Node* subLeft = _node->_right;
		while (subLeft->_left)
		{
			subLeft = subLeft->_left;
		}
		_node = subLeft;
	}
	else//右为空,向上找孩子是父亲左的那个父亲
	{
		Node* parent = _node->_parent;
		Node* cur = _node;
		while (parent && parent->_right == cur)
		{
			cur = parent;
			parent = cur->_parent;
		}
		_node = parent;
	}
	return *this;
}

Self operator++(int)
{
	Self tmp = *this;
	++*this;
	return tmp;
}

【C++练级之路】【Lv.17】【STL】set类和map类的模拟实现,进击的C++,c++,开发语言,stl,容器,数据结构

细节:

  1. 前置++的思路:
    • 右不为空,找右子树的最左结点
    • 右为空,向上找孩子是父亲左的那个父亲
  2. 后置++:复用前置++,返回临时对象

1.2.2 operator- -

Self& operator--()
{
	if (_node->_left)//左不为空,找左子树的最右结点
	{
		Node* subRight = _node->_left;
		while (subRight->_right)
		{
			subRight = subRight->_right;
		}
		_node = subRight;
	}
	else//左为空,向上找孩子是父亲右的那个父亲
	{
		Node* parent = _node->_parent;
		Node* cur = _node;
		while (parent && parent->_left == cur)
		{
			cur = parent;
			parent = cur->_parent;
		}
		_node = parent;
	}
	return *this;
}

Self operator--(int)
{
	Self tmp = *this;
	--*this;
	return tmp;
}

【C++练级之路】【Lv.17】【STL】set类和map类的模拟实现,进击的C++,c++,开发语言,stl,容器,数据结构

细节:

  1. 前置- -的思路:
    • 左不为空,找左子树的最右结点
    • 左为空,向上找孩子是父亲右的那个父亲
  2. 后置- -:复用前置- -,返回临时对象

1.3 本体

1.3.1 成员变量

template<class K, class T, class KeyOfT>
class RBTree
{
protected:
	typedef RBTreeNode<T> Node;
public:
protected:
	Node* _root = nullptr;
};

细节:

  1. 模板参数第一个为K,键值类型(比较时会用到)
  2. 模板参数第二个为T,同时适用set(存储键值)和map(存储键值对)
  3. 模板参数第三个为KeyOfT(仿函数类型),用于获取不同数据T的键值key来进行比较

1.3.2 begin和end

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

iterator begin()
{
	Node* cur = _root;
	while (cur->_left)
	{
		cur = cur->_left;
	}
	return iterator(cur);
}

const_iterator begin() const
{
	Node* cur = _root;
	while (cur->_left)
	{
		cur = cur->_left;
	}
	return const_iterator(cur);
}

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

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

细节:begin返回最左节点的迭代器,end返回空迭代器

1.3.3 Find

iterator Find(const K& key)
{
	if (_root == nullptr)
	{
		return iterator(nullptr);
	}

	KeyOfT kot;
	Node* cur = _root;
	while (cur)
	{
		if (kot(cur->_data) < key)
		{
			cur = cur->_right;
		}
		else if (kot(cur->_data) > key)
		{
			cur = cur->_left;
		}
		else
		{
			return iterator(cur);
		}
	}

	return iterator(nullptr);
}

细节:

  1. 返回迭代器
  2. 运用仿函数进行键值比较

1.3.4 Insert

pair<iterator, bool> Insert(const T& data)
{
	if (_root == nullptr)
	{
		_root = new Node(data);
		_root->_col = BLACK;
		return make_pair(iterator(_root), 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 make_pair(iterator(cur), false);
		}
	}

	Node* newnode = new Node(data);
	cur = newnode;
	if (kot(parent->_data) < kot(data))
	{
		parent->_right = cur;
	}
	else
	{
		parent->_left = cur;
	}
	cur->_parent = parent;

	while (parent && parent->_col == RED)
	{
		Node* grandparent = parent->_parent;
		if (grandparent->_right == parent)//uncle在左,parent在右
		{
			Node* uncle = grandparent->_left;
			if (uncle && uncle->_col == RED)//uncle为红,变色+向上调整
			{
				parent->_col = uncle->_col = BLACK;
				grandparent->_col = RED;

				cur = grandparent;
				parent = cur->_parent;
			}
			else//uncle为空或为黑,变色+旋转
			{
				if (parent->_right == cur)//左单旋
				{
					RotateL(grandparent);
					parent->_col = BLACK;
					grandparent->_col = RED;
				}
				else//右左旋
				{
					RotateR(parent);
					RotateL(grandparent);
					cur->_col = BLACK;
					grandparent->_col = RED;
				}
			}
		}
		else//parent在左,uncle在右
		{
			Node* uncle = grandparent->_right;
			if (uncle && uncle->_col == RED)
			{
				parent->_col = uncle->_col = BLACK;
				grandparent->_col = RED;

				cur = grandparent;
				parent = cur->_parent;
			}
			else
			{
				if (parent->_left == cur)//右单旋
				{
					RotateR(grandparent);
					parent->_col = BLACK;
					grandparent->_col = RED;
				}
				else//左右旋
				{
					RotateL(parent);
					RotateR(grandparent);
					cur->_col = BLACK;
					grandparent->_col = RED;
				}
			}
		}
	}
	_root->_col = BLACK;

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

细节:

  1. 返回pair,第一个参数为迭代器,第二个参数为布尔值(记录是否插入成功)
  2. 运用仿函数进行键值比较

二、set

2.1 成员变量与仿函数

template<class K>
class set
{
	struct SetKeyOfT
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};
public:
protected:
	RBTree<K, K, SetKeyOfT> _t;
};

细节:

  1. set类仿函数,直接返回参数key
  2. 成员变量的第二个模板参数为K,第三个模板参数为SetKeyOfT

2.2 begin和end

typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;
typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;

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

const_iterator begin() const
{
	return _t.begin();
}

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

const_iterator end() const
{
	return _t.end();
}

细节:

  1. 加上typename关键字,编译器才能识别类型
  2. set中存储的键值key均不允许修改,所以其普通迭代器和const迭代器均为红黑树的const迭代器
  3. 由于set的普通迭代器也是红黑树的const迭代器,调用普通begin()时,便有从普通迭代器到const迭代器的转换,此时之前写的拷贝构造(以普通迭代器拷贝构造const迭代器)便派上用场了。

2.3 find

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

2.4 insert

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

细节:

  1. 插入参数类型为K(键值)
  2. 此时也有从普通迭代器到const迭代器的转换

三、map

3.1 成员变量与仿函数

template<class K, class V>
class map
{
	struct MapKeyOfT
	{
		const K& operator()(const pair<const K, V>& kv)
		{
			return kv.first;
		}
	};
public:
protected:
	RBTree<K, pair<const K, V>, MapKeyOfT> _t;
};

细节:

  1. map类仿函数,返回参数pair的first
  2. 成员变量的第二个模板参数为pair,第三个模板参数为MapKeyOfT

3.2 begin和end

typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::iterator iterator;
typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::const_iterator const_iterator;

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

const_iterator begin() const
{
	return _t.begin();
}

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

const_iterator end() const
{
	return _t.end();
}

细节:

  1. 加上typename关键字,编译器才能识别类型
  2. map同样不允许修改key,故加上const修饰,但是允许修改存储的value,所以普通和const迭代器一一对应

此时,可能有人会问,那刚刚set不允许修改key,为什么不也直接用const修饰呢?请看以下这段代码:

typedef RBTreeIterator<T, const T&, const T*> const_iterator;

如果变成第二个模板参数T传入const K,那么就会形成两个连续的const,这是不被允许的。所以才想了其他方法来补救。

3.3 find

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

3.4 insert

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

细节:插入参数类型为pair(键值对)

3.5 operator[ ]

map最好用的重载运算符[ ],我们肯定也要实现,平常插入和修改使用[ ]更加方便。

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

细节:

  1. 插入成功便是插入,插入失败便是查找+修改
  2. 返回value的引用,可以直接插入或修改

【C++练级之路】【Lv.17】【STL】set类和map类的模拟实现,进击的C++,c++,开发语言,stl,容器,数据结构文章来源地址https://www.toymoban.com/news/detail-852808.html

真诚点赞,手有余香

到了这里,关于【C++练级之路】【Lv.17】【STL】set类和map类的模拟实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【C++练级之路】【Lv.4】类和对象(下)(初始化列表,友元,static成员,编译器的优化)

    欢迎各位小伙伴关注我的专栏,和我一起系统学习C++,共同探讨和进步哦! 学习专栏 : 《进击的C++》 在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。 虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象

    2024年02月04日
    浏览(40)
  • 【C++练级之路】【Lv.12】继承(你真的了解菱形虚拟继承吗?)

    快乐的流畅:个人主页 个人专栏:《C语言》《数据结构世界》《进击的C++》 远方有一堆篝火,在为久候之人燃烧! 继承(inheritance),是面向对象的三大特性之一。 它是面向对象编程中, 使代码可以复用 的最重要的手段,它允许程序员在 保持原有类特性的基础上进行扩展

    2024年03月14日
    浏览(35)
  • 【C++练级之路】【Lv.15】AVL树(双子旋转,领略绝对平衡之美)

    快乐的流畅:个人主页 个人专栏:《C语言》《数据结构世界》《进击的C++》 远方有一堆篝火,在为久候之人燃烧! 之前讲解了二叉搜索树,最优情况下它具有非常好的搜索性能,但是在极端场景下,它可能退化为单支树,可以形象地称为歪脖子树~ 这样的话,它搜索的时间

    2024年03月25日
    浏览(31)
  • 【C++练级之路】【Lv.14】二叉搜索树(进化的二叉树——BST)

    快乐的流畅:个人主页 个人专栏:《C语言》《数据结构世界》《进击的C++》 远方有一堆篝火,在为久候之人燃烧! 二叉树在之前的数据结构章节讲解过,当时使用C来实现。而如今学习的二叉搜索树,便是二叉树的进阶,也更适合使用C++来实现。 二叉搜索树(BST,Binary Se

    2024年03月23日
    浏览(26)
  • 【C++练级之路】【Lv.16】红黑树(冰与火的碰撞,红与黑的史诗)

    快乐的流畅:个人主页 个人专栏:《C语言》《数据结构世界》《进击的C++》 远方有一堆篝火,在为久候之人燃烧! 之前学习的AVL树,是一种平衡二叉搜索树,它追求绝对平衡,从而导致插入和删除性能较差。而今天学习的红黑树,是另一种平衡二叉搜索树,它追求相对平衡

    2024年04月09日
    浏览(51)
  • 【C++练级之路】【Lv.20】位图和布隆过滤器(揭开大数据背后的神秘面纱)

    快乐的流畅:个人主页 个人专栏:《算法神殿》《数据结构世界》《进击的C++》 远方有一堆篝火,在为久候之人燃烧! 哈希映射 的思想,在实际中有许多运用,之前介绍的 哈希表 是一种经典的应用场景,而今天我们将了解其他的哈希数据结构—— 位图和布隆过滤器 ,它

    2024年04月12日
    浏览(31)
  • 【C++】 使用红黑树模拟实现STL中的map与set

    前面的文章我们学习了红黑树,也提到了C++STL中的map和set的底层其实就是用的红黑树来实现的(而map和set的使用我们前面也学过了)。 既然红黑树我们也学习过了,那这篇文章我们就用红黑树来简单实现一下STL中的map和set,重点是学习它的框架。 上一篇文章我们实现了红黑

    2024年02月12日
    浏览(20)
  • 【C++打怪之路Lv3】-- 类和对象(上)

    🌈 个人主页:白子寰 🔥 分类专栏: C++打怪之路,python从入门到精通,数据结构,C语言,C语言题集 👈 希望得到您的订阅和支持~ 💡 坚持创作博文(平均质量分82+),分享更多关于深度学习、C/C++,python领域的优质内容!(希望得到您的关注~)    目录 面向对象和面向过程

    2024年04月27日
    浏览(31)
  • 【C++ STL之map,set,pair详解】

    在C++的STL(Standard Template Library)中,map是一个非常有用的关联容器。它提供了一种键-值对的数据结构,其中的元素按照键的顺序进行排序,并且每个键是唯一的。本文将详细介绍C++ STL中map的使用方法和一些常见操作。 (1)头文件 (2)初始化方法 可以使用以下方式声明和

    2024年02月12日
    浏览(22)
  • 【C++】STL之map、set类源码剖析

    目录 概述 算法 源码 Iterator.h RBTree.h Map.h Set.h test.cpp map和set都是STL中的关联式容器,而vector、list、deque是序列式容器。 map是映像,set是集合,map元素的数据类型是std::pairK,V格式(key/value形成映像),set元素的数据类型只有key值。 map和set的实现是对红黑树的封装,map根据key值进行

    2024年02月02日
    浏览(28)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包