set和map + multiset和multimap(使用+封装(RBTree))

set和map

  • 前言
  • 一、使用
    • 1. set
      • (1)、模板参数列表
      • (2)、常见构造
      • (3)、find和count
      • (4)、insert和erase
      • (5)、iterator
      • (6)、lower_bound和upper_bound
    • 2. multiset
    • 3. map
      • (1)、模板参数列表
      • (2)、构造
      • (3)、modifiers和operations
      • (4)、operator[]
    • 4. multimap
  • 二、封装
    • RBTree
      • 迭代器原理
      • RBTree实现代码
    • map
    • set
  • 三、总结

前言

本文介绍的是树型关联式容器。
关联式容器:用来存储数据,存储的是<key, value>结构的键值对,在检索时效率更高。主要有这四种:map,set,multimap,multiset。

键值对:用来标识具有一一对应关系的结构,该结构一般包含两个成员变量key和value,key表示键值,value表示与key对应的信息。

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

//pair底层
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)
	{}
};

一、使用

1. set

(1)、模板参数列表

模板参数列表

(2)、常见构造

void test_constructor()
{
	set<int> s1;                            //无参构造

	int arr[] = { 10,20,30,40,50 };
	set<int> s2(arr, arr + 5);              //数组范围构造
	set<int> s3(s2.begin(), s2.end());      //迭代器区间构造

	set<int> s4(s3);                        //拷贝构造
}

(3)、find和count

void test_find()
{
	set<int> s;
	s.insert(5);
	s.insert(10);
	s.insert(8);
	s.insert(2);

	//find return value: iterator(val is found)
	//otherwise set::end 
	if (s.find(5) != s.end())
	{
		cout << "find:找到了" << endl;      
	}

	//count return value:1 (val is found),or 0 otherwise
	if (s.count(5))
	{
		cout << "count:找到了" << endl;     
	}
}

(4)、insert和erase

void test_modify()
{
	//insert
	//去重+排序
	set<int> s;
	s.insert(10);
	s.insert(5);
	s.insert(6);
	s.insert(5);
	s.insert(5);
	s.insert(7);
	s.insert(2);

	for (auto e : s)
	{
		cout << e << " ";     //2 5 6 7 10
	}
	cout << endl;

	//迭代器
	set<int>::iterator sit;
	pair<set<int>::iterator, bool> ret;   //接收插入返回值

	//pair<iterator, bool> insert(const value_type & val);        insert参数列表
	ret = s.insert(1);
	if (ret.second == true)
		sit = ret.first;
	cout << *sit << endl;    //1
	
	ret = s.insert(1);
	if (ret.second == false) 
		sit = ret.first;
	cout << *sit << endl;    //1


	//iterator insert(iterator position, const value_type & val);  insert参数列表
	sit = s.insert(sit, 20);
	cout << *sit << endl;   //20

	//	template <class InputIterator>
	//void insert(InputIterator first, InputIterator last);        insert参数列表
	int arr[] = { 0,10,15 };            // 10 already in set, not inserted
	s.insert(arr, arr + 3);
	for (auto e : s)
	{
		cout << e << " ";     //0 1 2 5 6 7 10 15 20
	}
	cout << endl;

	///
	//erase

	//void erase(iterator position);               erase参数列表
	s.erase(sit);    //*sit = 20

	//size_type erase(const value_type & val);     erase参数列表
	int e_ret = s.erase(0);
	cout << e_ret << endl;
	for (auto e : s)
	{
		cout << e << " ";     //1 2 5 6 7 10 15
	}
	cout << endl;

	//void erase(iterator first, iterator last);   erase参数列表
	s.erase(s.begin(), s.end());
	for (auto e : s)
	{
		cout << e << " ";     //empty
	}
	cout << endl;
}

(5)、iterator

void test_iteator()
{
	int arr[] = { 10,20,30,40,50 };
	set<int> s(arr, arr + 5);

	set<int>::iterator it = s.begin();
	set<int>::const_iterator cit = s.cbegin();
	set<int>::reverse_iterator rit = s.rbegin();
	set<int>::const_reverse_iterator crit = s.crbegin();

	while (it != s.end())
	{
		cout << *it << " ";   //10 20 30 40 50
		it++;
	}
	cout << endl;

	while (cit != s.cend())
	{
		cout << *cit << " ";   //10 20 30 40 50
		cit++;
	}
	cout << endl;

	while (rit != s.rend())
	{
		cout << *rit << " ";   //50 40 30 20 10
		rit++;
	}
	cout << endl;

	while (crit != s.crend())
	{
		cout << *crit << " ";  //50 40 30 20 10
		crit++;
	}
	cout << endl;
}

(6)、lower_bound和upper_bound

//iterator lower_bound(const value_type & val) const;                  lower_bound的声明
//iterator upper_bound(const value_type & val) const;                  upper_bound的声明
//pair<iterator, iterator> equal_range(const value_type& val) const;   equal_range的声明
void test_bound()
{
	set<int> s;
	set<int>::iterator itlow, itup;

	for (size_t i = 1; i < 10; i++)
	{
		s.insert(i * 10);               //10 20 30 40 50 60 70 80 90
	}


	//左闭右开[30,70)
	itlow = s.lower_bound(30);
	itup = s.upper_bound(60);

	cout << *itlow << endl;        //30
	cout << *itup << endl;         //70


	set<int>::iterator it = s.lower_bound(35);
	//   s > 35
	cout << *it << endl;           //40
	//*it = 50;     //不能修改,保护键值

	s.erase(itlow, itup);

	for (auto e : s)
	{
		cout << e << " ";             //10 20 70 80 90
	}
	cout << endl;

//
	//equal_range - most_use multiset

	//pair<set<int>::const_iterator, set<int>::const_iterator> 
	auto ret1 = s.equal_range(15);
	itlow = ret1.first;
	itup = ret1.second;
	//因为不存在15,所以itlow和itup是一段不存在的区间
	cout << *itlow << endl;      //20   
	cout << *itup << endl;       //20   左开右闭


	auto ret2 = s.equal_range(80);
	itlow = ret2.first;
	itup = ret2.second;
	//[80,90)
	cout << *itlow << endl;     //80   
	cout << *itup << endl;      //90

	auto ret = s.equal_range(90);
	itlow = ret.first;
	itup = ret.second;
	//程序直接崩溃,到最后了
	cout << *itlow << endl;   //90     
	cout << *itup << endl;    //end()
}

2. multiset

这里的演示就不和set一样分开表示了,主要是multiset不去重

void test_multiset()
{
	//排序
	int arr[] = { 7, 7, 7, 3, 6, 5, 2, 3, 3, 3 };
	multiset<int> s(arr, arr + sizeof(arr) / sizeof(arr[0]));

	//不去重
	for (auto& e : s)
	{
		cout << e << " ";
	}
	cout << endl;

	multiset<int>::iterator pos = s.find(3);  //返回中序遍历的第一个3
	while (pos != s.end())           //find失败返回end()
	{
		//*pos = 10;    //err  不能修改,保护键值
		cout << *pos << " ";
		++pos;
	}
	cout << endl;

	cout << s.count(3) << endl;   //4个3



	pair<multiset<int>::iterator, multiset<int>::iterator> ret = s.equal_range(7);
	multiset<int>::iterator itlow = ret.first;
	multiset<int>::iterator itup = ret.second;

	// [itlow, itup)
	// [7,end())  s.equal_range(7);
	//cout << *itlow << endl;
	//cout << *itup << endl;      //error   *itup没有值

	// [itlow, itup)
	// [5,5)  s.equal_range(4); 
	ret = s.equal_range(4);
	itlow = ret.first;
	itup = ret.second;
	cout << *itlow << endl;
	cout << *itup << endl;       //ok

	s.erase(itlow, itup);   //没有进行删除   [5, 5)

	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl;
}

3. map

(1)、模板参数列表

模板参数列表

(2)、构造

void test_constructor()
{
	map<string, string> dict;
	//"insert"和"插入"都分别有隐式类型转换,const char* 转成const string&
	//不能直接进行隐式类型转换,原因:多参数
	pair<string, string> kv1("insert", "插入");
	dict.insert(kv1);

	dict.insert(pair<string, string>("sort", "排序"));  //匿名对象

	//常用
	// C++98
	dict.insert(make_pair("string", "字符串"));
	// C++11 多参数的构造函数隐式类型转换
	dict.insert({ "string", "字符串" });   //{}会自动调用pair的构造

	// 隐式类型的转换  构造+拷贝构造(优化)
	string str1 = "hello";
	pair<string, string> kv2 = { "string", "字符串" };

	//const pair<string, string>& kv2 = { "string", "字符串" };  引用一个临时变量
}

(3)、modifiers和operations

void test_modifiers()
{
	map<string, string> dict;
	dict.insert(make_pair("string", "字符串"));
	dict.insert(make_pair("insert", "插入"));
	dict.insert(make_pair("success", "成功"));

	//key已经有了就不会插入    //pair<iterator,bool> insert (const value_type& val);  插入的参数列表
	pair<map<string, string>::iterator, bool> ret = dict.insert(make_pair("insert", "xxx"));
	if (ret.second == false) {
		cout << "element already existed:";
		cout << ret.first->first << ":" << ret.first->second << endl;
	}

	//iterator insert (iterator position, const value_type& val);                      插入的参数列表
	map<string, string>::iterator it = dict.begin();
	it = dict.insert(it, make_pair("begin", "开始"));  // max efficiency inserting
	cout << it->first << ":" << it->second << endl;


	/*template <class InputIterator>
	void insert(InputIterator first, InputIterator last);*/                             //插入的参数列表
	map<string, string> copymap;
	//iterator find (const key_type& k); //查找的参数列表,返回值是查找这个位置的迭代器,如果没查找到,返回end()
	copymap.insert(dict.begin(), dict.find("success"));        


	it = dict.begin();
	while (it != dict.end())
	{
		//it->first = "xxx";   //error
		//it->second = "sss";  //ok

		//cout << (*it).first << ":" << (*it).second << endl;
		cout << it->first << ":" << it->second << endl;
		++it;
	}
	cout << endl;


	int number = dict.erase("sucess");
	cout << number << endl;

	for (const auto& e : dict)
	{
		cout << e.first << ":" << e.second << endl;
	}
}

(4)、operator[]

注意key不存在,operator[]是插入,at是抛异常

void test_operator()
{
	string arr[] = { "西瓜", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
	map<string, int> countMap;
	for (const auto& e : arr)
	{
		auto it = countMap.find(e);
		if (it == countMap.end())
		{
			countMap.insert(make_pair(e, 1));   //首次插入
		}
		else
		{
			it->second++;                       //统计次数
		}

	}

	//pair<map<char, int>::iterator, bool>::iterator it = this->insert(make_pair(k,mapped_type()))
	//(*(it.first)).second
	for (const auto& e : arr)
	{
		//查找e是否存在,如果不存在进行插入,如果存在,返回value
		countMap[e]++;
	}

	for (const auto& kv : countMap)
	{
		cout << kv.first << ":" << kv.second << endl;
	}
}

4. multimap

multimap和map的唯一不同,前者的key可以重复

二、封装

set和map的封装的底层结构使用的都是红黑树(这篇博客介绍了红黑树的旋转),在STL中set底层实际存储的数据是键值对< value, value >,这样就可以调用同个红黑树。

RBTree

迭代器原理

红黑树

双向迭代器 -> 根据红黑树的特征:

  1. 迭代器++,只需要判断当前位置的右侧节点的情况
  2. 迭代器- -,只需要判断当前位置的左侧节点的情况
  1. 迭代器++
  1. 右孩子不为空,访问右子树的最左节点(最小节点)。
  2. 右孩子为空,下一个访问的是孩子是父亲左的祖先节点。

代码实现:

Self& operator++()
{
	//右不为空
	if (_node->_right)
	{
		//右子树的最左节点(右子树最小节点)
		Node* subLeft = _node->_right;
		while (subLeft->_left)
		{
			subLeft = subLeft->_left;
		}

		_node = subLeft;
	}
	else  //右为空
	{
		Node* cur = _node;
		Node* parent = cur->_parent;
		//父节点为空,或者当前节点不是父节点的左孩子,循环继续
		while (parent && cur == parent->_right)
		{
			cur = parent;
			parent = parent->_parent;
		}
		
		//parent为空的情况和找到下一个节点的情况
		_node = parent;
	}
	return *this;
}
  1. 迭代器–
  1. 左孩子不为空,访问左子树的最右节点(最大节点)。
  2. 左孩子为空,下一个访问的是孩子是父亲右的祖先节点。
Self& operator--()
{
	//左孩子不为空
	if (_node->_left)
	{
		Node* subRight = _node->_left;
		while (subRight->_right)
		{
			subRight = subRight->_right;
		}

		_node = subRight;
	}
	else  //左孩子为空
	{ 
		//孩子是父亲右的那个节点
		Node* cur = _node;
		Node* parent = cur->_parent;
		
		while (parent && cur == parent->_left)
		{
			cur = parent;
			parent = parent->_parent;
		}
		
		//parent为空的情况和找到下一个节点的情况
		_node = parent;
	}
	return *this;
}

RBTree实现代码

//节点的颜色
enum Color
{
	RED,
	BLACK
};

//这里一个模板参数T就可以,这个T既是set的key,也是map的value
template<class T>
struct RBTreeNode
{
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;
	T _data;
	Color _color;

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


//迭代器
template<class T, class Ptr, class Ref>
struct __TreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef __TreeIterator<T, Ptr, Ref> Self;

	//无论被实例化成什么,都是普通迭代器
	typedef __TreeIterator<T, T*, T&> Iterator;
	
	//这个类被实列化成const迭代器时,这个函数是一个构造,支持普通迭代器构造const迭代器
	//这个类被实列化成普通迭代器时,这个函数是一个拷贝构造
	__TreeIterator(const Iterator& it)
		:_node(it._node)
	{}

	Node* _node;

	//节点初始化
	__TreeIterator(Node* node)
		:_node(node)
	{}

	Ref operator*()
	{
		return _node->_data;
	}
	Ptr operator->()
	{
		return &_node->_data;
	}
	bool operator!=(const Self& s)
	{
		return _node != s._node;
	}

	Self& operator--()
	{
		if (_node->_left)
		{
			Node* subRight = _node->_left;
			while (subRight->_right)
			{
				subRight = subRight->_right;
			}

			_node = subRight;
		}
		else  
		{ 
			//孩子是父亲右的那个节点
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && cur == parent->_left)
			{
				cur = parent;
				parent = parent->_parent;
			}
			//parent为空的情况和找到下一个节点的情况
			_node = parent;
		}
		return *this;
	
	}


	Self& operator++()
	{
		//右不为空
		if (_node->_right)
		{
			//右子树的最左节点(右子树最小节点)
			Node* subLeft = _node->_right;
			while (subLeft->_left)
			{
				subLeft = subLeft->_left;
			}

			_node = subLeft;
		}
		else  //右为空
		{
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && cur == parent->_right)
			{
				cur = parent;
				parent = parent->_parent;
			}
			//parent为空的情况和找到下一个节点的情况
			_node = parent;
		}
		return *this;
	}
};


//set->RBTree<K, K, SetKeyOfT> _t;
//map->RBTree<K, pair<K, V>, MapKeyOfT> _t;

//KeyOfT是上层传下来的仿函数
template<class K, class T, class KeyOfT>
class RBTree
{
	typedef RBTreeNode<T> Node;
public:
	typedef __TreeIterator<T, T*, T&> iterator;
	typedef __TreeIterator<T, const T*, const T&> const_iterator;

	iterator begin()
	{
		Node* leftMin = _root;
		while (leftMin && leftMin->_left)
		{
			leftMin = leftMin->_left;
		}
		return iterator(leftMin);
	}
	iterator end()
	{
		//区分,这里和STL源码中的结束方式不同,
		return iterator(nullptr);
	}

	const_iterator begin() const
	{
		Node* leftMin = _root;
		while (leftMin && leftMin->_left)
		{
			leftMin = leftMin->_left;
		}
		return iterator(leftMin);
	}
	const_iterator end() const
	{
		return iterator(nullptr);
	}


	//传K的作用
	Node* 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 cur;
			}
		}

		//没找到,返回nullptr
		return nullptr;
	}

	//注意insert的返回值是一个键值对
	pair<iterator, bool> Insert(const T& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_color = BLACK;
			return make_pair(iterator(_root), true);
		}

		//寻找要链接新节点的位置
		Node* parent = nullptr;
		Node* cur = _root;

		KeyOfT kot;
		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);
			}
		}

		//插入节点 + 链接
		cur = new Node(data);
		cur->_color = RED;

		//保存节点,用于返回
		Node* newnode = cur;

		if (kot(parent->_data) > kot(data))
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}

		cur->_parent = parent;


		//调整   这里parent是否为空,是为了下一次循环判断
		//       如果parent->_color == BLACK也不用玩了
		while (parent && parent->_color == RED)
		{
			Node* grandfather = parent->_parent;
			if (grandfather->_left == parent)
			{
				Node* uncle = grandfather->_right;
				//u为红
				if (uncle && uncle->_color == RED)
				{
					parent->_color = uncle->_color = BLACK;
					grandfather->_color = RED;

					//继续向上调整
					cur = grandfather;
					parent = cur->_parent;
				}
				else //u不存在 或 存在且为黑
				{
					if (cur == parent->_left)
					{
						//      g
						//   p
						//c
						RotateR(grandfather);
						parent->_color = BLACK;
						grandfather->_color = RED;
					}
					else
					{
						//      g
						//   p
						//      c	
						RotateL(parent);
						RotateR(grandfather);
						cur->_color = BLACK;
						grandfather->_color = RED;
					}

					//调整完之后,就不需要继续改变了
					break;
				}
			}
			else   //grandfather->_right == parent
			{
				Node* uncle = grandfather->_left;
				//u为红
				if (uncle && uncle->_color == RED)
				{
					parent->_color = uncle->_color = BLACK;
					grandfather->_color = RED;

					//继续向上调整
					cur = grandfather;
					parent = cur->_parent;
				}
				else //u不存在 或 存在且为黑
				{
					if (cur == parent->_right)
					{
						//g
						//   p
						//      c
						RotateL(grandfather);
						parent->_color = BLACK;
						grandfather->_color = RED;
					}
					else
					{
						//g
						//   p
						//c	
						RotateR(parent);
						RotateL(grandfather);
						cur->_color = BLACK;
						grandfather->_color = RED;
					}

					//调整完之后,就不需要继续改变了
					break;
				}
			}
		}

		//根节点的颜色改成黑色
		_root->_color = BLACK;

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

	//判断该树是不是红黑树
	bool IsBalance()
	{
		return _IsBalance(_root);
	}

	//计算红黑树的高度
	int Height()
	{
		return Height(_root);
	}


private:
	int Height(Node* root)
	{
		if (root == nullptr)
			return 0;

		int leftHeight = Height(root->_left);
		int rightHeight = Height(root->_right);

		return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
	}


	bool CheckColor(Node* root, int blacknum, int benchmark)
	{
		if (root == nullptr)
		{
			if (blacknum != benchmark)
			{
				return false;
			}
			return true;
		}

		//计算每条路径的黑色节点
		if (root->_color == BLACK)
		{
			++blacknum;
		}

		if (root->_color == RED && root->_parent && root->_parent->_color == RED)
		{
			cout << root->_kv.first << "出现连续红色节点" << endl;
			return false;
		}


		return CheckColor(root->_left, blacknum, benchmark)
			&& CheckColor(root->_right, blacknum, benchmark);
	}


	bool _IsBalance(Node* root)
	{
		if (root == nullptr)
		{
			return true;
		}

		if (root->_color != BLACK)
		{
			return false;
		}

		//基准值 -->  用于比较别的路径黑色节点个数
		int benchmark = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_color == BLACK)
				benchmark++;
			cur = cur->_left;
		}

		return CheckColor(root, 0, benchmark);

	}


	//旋转
	//都是二叉树的旋转,所以和AVLTree的旋转一样,只不过这里没有平衡因子
	void RotateR(Node* parent)
	{
		Node* cur = parent->_left;
		Node* curright = cur->_right;

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


		Node* ppnode = parent->_parent;

		cur->_right = parent;


		parent->_parent = cur;

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

			cur->_parent = ppnode;
		}
	}

	void RotateL(Node* parent)
	{
		Node* cur = parent->_right;
		Node* curleft = cur->_left;

		//重新链接
		parent->_right = curleft;
		if (curleft)
			curleft->_parent = parent;

		cur->_left = parent;

		//提前保存parent->_parent,可能是根节点,也可能是子树的根节点
		Node* ppnode = parent->_parent;

		parent->_parent = cur;

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

			cur->_parent = ppnode;
		}

	}


private:
	Node* _root = nullptr;
};

map

namespace kpl
{
	template<class K, class V>
	class map
	{
		//RBTree仿函数的主要作用在这里,set的封装只是跟跑
		struct MapKeyOfT
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;
			}
		};

	public:
		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();
		}

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

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

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

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

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

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

set

namespace kpl
{
	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;

		//set只保留一个const即可
		const_iterator begin() const
		{
			return _t.begin();
		}

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


		pair<iterator, bool> insert(const K& key)
		{
			//这里返回值的first的迭代器是普通迭代器,用普通迭代器接收
			pair<typename RBTree<K, K, SetKeyOfT>::iterator, bool> ret = _t.Insert(key);

			//使用普通迭代器构造一个const的迭代器,这里就体现出迭代器实现中的那个拷贝构造
			return pair<iterator, bool>(ret.first, ret.second);
		}

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

三、总结

set

  1. 插入的元素只需要value,不用键值对
  2. set中的元素不能重复(set可以去重)
  3. 单个元素的访问速度比unordered_set慢
  4. 中序遍历有序,使用其迭代器访问也是有序
  5. 不允许修改,破坏结构

map

  1. map中的元素是键值对
  2. map中的key是唯一的,不能修改,但是value可以修改
  3. 中序遍历有序,使用其迭代器访问也是有序
  4. 支持operator[]
  5. 单个元素的访问速度比unordered_set慢

multiset和multimap(区分set和map)
multiset的value可以重复,multimap的key也可以重复

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

(11_23)构建高效数据流转的 ETL 系统:数据库 + Serverless 函数计算的最佳实践

作者&#xff5c;柳下 概述 随着企业规模和数据量的增长&#xff0c;数据的价值越来越受到重视。数据的变化和更新变得更加频繁和复杂&#xff0c;因此及时捕获和处理这些变化变得至关重要。为了满足这一需求&#xff0c;数据库 CDC&#xff08;Change Data Capture&#xff…

晶振为什么不能放置在PCB边缘

某行车记录仪&#xff0c;测试的时候要加一个外接适配器&#xff0c;在机器上电运行测试时发现辐射超标&#xff0c;具体频点是84MHz、144MHz、168MHz&#xff0c;需要分析其辐射超标产生的原因&#xff0c;并给出相应的对策。辐射测试数据如下&#xff1a; 图1&#xff1a;辐…

B/S前后端分离的Java医院云HIS信息管理系统源码(LIS源码+电子病历源码)

HIS系统采用主流成熟技术开发&#xff0c;软件结构简洁、代码规范易阅读&#xff0c;SaaS应用&#xff0c;全浏览器访问前后端分离&#xff0c;多服务协同&#xff0c;服务可拆分&#xff0c;功能易扩展。多医院、多集团统一登录患者主索引建立、主数据管理&#xff0c;统一对外…

鸿蒙开发-ArkTS 语言-状态管理

鸿蒙开发-ArkTS 语言-基础语法 3. 状态管理 变量必须被装饰器装饰才能成为状态变量&#xff0c;状态变量的改变才能导致 UI 界面重新渲染 概念描述状态变量被状态装饰器装饰的变量&#xff0c;改变会引起UI的渲染更新。常规变量没有状态的变量&#xff0c;通常应用于辅助计算…

CVE-2023-22515:Atlassian Confluence权限提升漏洞复现 [附POC]

文章目录 Atlassian Confluence权限提升(CVE-2023-22515)漏洞复现 [附POC]0x01 前言0x02 漏洞描述0x03 影响版本0x04 漏洞环境0x05 漏洞复现1.访问漏洞环境2.构造POC3.复现 0x06 修复建议 Atlassian Confluence权限提升(CVE-2023-22515)漏洞复现 [附POC] 0x01 前言 免责声明&…

Unity-类-Vector

Vector矢量 是一个基本的数学概念,它允许你描述方向和大小。在游戏和应用中,矢量通常用于描述一些基本属性,如角色的位置、物体移动的速度或两个物体之间的距离。 矢量算术是计算机编程很多方面(如图形、物理和动画)的基础,深入了解这一主题对于充分发挥 Unity 的功能很有…

从零学算法400

400.给你一个整数 n &#xff0c;请你在无限的整数序列 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, …] 中找出并返回第 n 位上的数字。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;3 示例 2&#xff1a; 输入&#xff1a;n 11 输出&#xff1a;0 解释&#xff1a;第…

社交媒体广告数据采集:Jsoup 的最佳实践

搜狐是中国领先的综合门户网站之一&#xff0c;广告在其网站上广泛投放。为了了解搜狐广告的策略和趋势&#xff0c;采集和分析搜狐广告数据变得至关重要。但是&#xff0c;搜狐网站的广告数据通常需要通过网页抓取的方式获取&#xff0c;这就需要一个强大的工具来解析和提取数…

HTML4总结

一、前序知识 1. 认识两位先驱 2. 计算机基础知识 1. 计算机俗称电脑&#xff0c;是现代一种用于高速计算的电子计算机器&#xff0c;可以进行数值计算、逻辑计算&#xff0c;还 具有存储记忆功能。 2. 计算机由 硬件 软件 成&#xff1a; 硬件&#xff1a;看得见摸得着…

tp8 使用rabbitMQ(2)工作队列

代码的参数说明在 第一小节的代码中&#xff0c;如果需要可移步到第一节中查看 工作队列 工作队列&#xff08;又称&#xff1a;任务队列——Task Queues&#xff09;是为了避免等待一些占用大量资源、时间的操作。当我们把任务&#xff08;Task&#xff09;当作消息发送到队列…

计算机思考与整理

应用程序 虚拟机 windows,linux等操作系统&#xff08;向上层应用程序提供接口&#xff09; x86架构&#xff0c;MIPS&#xff0c;ARM(提供指令集) 硬件组件 硬件组件&#xff08;hardware components&#xff09;是指构成计算机或电子设备的实体部分&#xff0c;它们包括各…

双向链表超详解——连我奶奶都能学会的复杂链表(带头双向循环)

文章目录 前言一、双向链表的概念二、双向链的结构设计三、双链表的基本功能接口四、双向链表接口的实现4.1、创建结点4.2、初始化链表4.3、打印链表4.4、尾插结点4.5、尾删结点4.6、头插结点4.7、头删结点4.8、在pos结点前面插入4.9、删除pos位置的结点4.10、查找链表中的某个…

spring aop核心原理概念

目录 概述aop核心概念解析Target(目标对象)Joinpoint(连接点)Advice(通知/增加)Pointcut(切入点)Aspect(切面)Advisor(通知器)Weaving(织入)Proxy(代理)Introduction(引介) 结束 概述 aop核心概念解析 Target(目标对象) 代理的目标对象 目标对象(Target)的确立&#xff0c;是…

云计算领域的第三代浪潮!

根据IDC不久前公布的数据&#xff0c;2023年上半年中国公有云服务整体市场规模(IaaS/PaaS/SaaS)为190.1亿美元&#xff0c;阿里云IaaS、PaaS市场份额分别为29.9%和27.9%&#xff0c;都远超第二名&#xff0c;是无可置疑的行业领头羊。 随着人工智能&#xff08;AI&#xff09;…

面试题:什么是自旋锁?自旋的好处和后果是什么呢?

文章目录 什么是自旋自旋和非自旋的获取锁的流程 自旋锁的好处AtomicLong 的实现实现一个可重入的自旋锁示例自旋的缺点适用场景 什么是自旋 “自旋”可以理解为“自我旋转”&#xff0c;这里的“旋转”指“循环”&#xff0c;比如 while 循环或者 for 循环。“自旋”就是自己…

pwn:[NISACTF 2022]ReorPwn?

题目 按正常方式走&#xff0c;发现指令被反着输出

抓住机会:2024年企业生成式AI应用的未来

在 Menlo Ventures 的AI趋势研究报告中&#xff0c;对美国和欧洲的 450 多名企业高管进行了调查&#xff0c;并与另外十几位高管进行了交谈&#xff0c;以了解当今企业应用AI的状况。尽管大肆宣传&#xff0c;与其他软件类别相比&#xff0c;企业对生成式AI的投资仍然小得惊人。…

qgis添加arcgis的mapserver

左侧浏览器-ArcGIS地图服务器-右键-新建连接 Folder: / 展开-双击图层即可

Node.js入门指南(三)

目录 Node.js 模块化 介绍 模块暴露数据 导入模块 导入模块的基本流程 CommonJS 规范 包管理工具 介绍 npm cnpm yarn nvm的使用 我们上一篇文章介绍了Node.js中的http模块&#xff0c;这篇文章主要介绍Node.js的模块化&#xff0c;包管理工具以及nvm的使用。 Node…

排序算法:归并排序、快速排序、堆排序

归并排序 要将一个数组排序&#xff0c;可以先将它分成两半分别排序&#xff0c;然后再将结果合并&#xff08;归并&#xff09;起来。这里的分成的两半&#xff0c;每部分可以使用其他排序算法&#xff0c;也可以仍然使用归并排序&#xff08;递归&#xff09;。 我看《算法》…