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

news2024/11/15 8:50:05

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也可以重复

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

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

相关文章

2023年P气瓶充装证模拟考试题库及P气瓶充装理论考试试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2023年P气瓶充装证模拟考试题库及P气瓶充装理论考试试题是由安全生产模拟考试一点通提供&#xff0c;P气瓶充装证模拟考试题库是根据P气瓶充装最新版教材&#xff0c;P气瓶充装大纲整理而成&#xff08;含2023年P气瓶…

云原生基础概述

云原生的理解 在云原生这个概念还没流行起来的时候&#xff0c;公司开发了一款应用&#xff0c;这是「原生应用」&#xff0c;一般来说传统部署应用的方式都是通过购买物理的服务器&#xff0c;然后将需要上线的项目部署到物理服务器中跑起来。而购买物理机器&#xff0c;还要…

指针运算详解

1.引入 指针的基本运算有三种&#xff0c;分别是&#xff1a; • 指针- 整数 • 指针-指针 • 指针的关系运算 2.指针- 整数 因为数组在内存中是连续存放的&#xff0c;只要知道第⼀个元素的地址&#xff0c;顺藤摸⽠就能找到后⾯的所有元素。 int arr[10] {1,2,3,4,5,…

开发者神器-GitHub Copilot

关于费用 Copilot认证地址&#xff1a;https://web.52shizhan.cn/activity/copilot ① 无需账号&#xff0c;100%认证成功。 ② 支持windows、mac、linux系统。 ③ 可激活Jetbrains全系列,VsCode和Android Studio的Copilot插件&#xff0c;一键认证&#xff0c;100%秒成功。 …

NPU、CPU、GPU算力及算力计算方式

NVIDIA在9月20日发布的NVIDIA DRIVE Thor 新一代集中式车载计算平台&#xff0c;可在单个安全、可靠的系统上运行高级驾驶员辅助应用和车载信息娱乐应用。提供 2000 万亿次浮点运算性能&#xff08;2000 万亿次8位浮点运算&#xff09;。NVIDIA当代产品是Orin&#xff0c;算力是…

抽象类, 接口, Object类 ---java

目录 一. 抽象类 1.1 抽象类概念 1.2 抽象类语法 1.3 抽象类特性 1.4 抽象类的作用 二. 接口 2.1 接口的概念 2.2 语法规则 2.3 接口的使用 2.4 接口间的继承 2.5 抽象类和接口的区别 三. Object类 3.1 toString() 方法 3.2 对象比较equals()方法 3.3 hash…

24V转5V负压-5V3A降压芯片WT6019

24V转5V负压-5V3A降压芯片WT6019 WT6019这个小家伙&#xff0c;电压输入范围从6V到30V&#xff0c;能给你提供3A的电流&#xff0c;稳稳的&#xff01;而且&#xff0c;它的开关频率高达130kHz&#xff0c;让动态响应飞快&#xff0c;让你的设备随时保持冷静。WT6019转负压5V-5…

H5ke12--1--iframe标签制作页面的使用

上次说到 如何我们的数据html页面返回到服务器,服务器到html.submit不要了,直接button普通按钮,action也不用,,,fetch直接异步请求,那么就会有数据发送到服务器 Repones.write写入就行了,直接写的就是html页面演示 这次我们来看iframe, H5加入了传输页面的新的事件 注意 link、…

Python之Pygame游戏编程详解

一、介绍 1.1 定义 Pygame是一种流行的Python游戏开发库&#xff0c;它提供了许多功能&#xff0c;使开发人员可以轻松创建2D游戏。它具有良好的跨平台支持&#xff0c;可以在多个操作系统上运行&#xff0c;例如Windows&#xff0c;MacOS和Linux。在本文中&#xff0c;我们将…

【机器学习】聚类(一):原型聚类:K-means聚类

文章目录 一、实验介绍1. 算法流程2. 算法解释3. 算法特点4. 应用场景5. 注意事项 二、实验环境1. 配置虚拟环境2. 库版本介绍 三、实验内容0. 导入必要的库1. Kmeans类a. 构造函数b. 闵可夫斯基距离c. 初始化簇心d. K-means聚类e. 聚类结果可视化 2. 辅助函数3. 主函数a. 命令…

Jetson Orin Nano 内核编译

首先是安装编译环境所需的依赖 sudo apt-get install git bison flex libssl-dev zip libncurses-dev makesudo apt-get install build-essential bc下载交叉编译器以及代码&#xff0c;官方链接: link https://developer.nvidia.com/embedded/jetson-linux 解压下载的两个文件…

JVM之jvisualvm多合一故障处理工具

jvisualvm多合一故障处理工具 1、visualvm介绍 VisualVM是一款免费的&#xff0c;集成了多个 JDK 命令行工具的可视化工具&#xff0c;它能为您提供强大的分析能力&#xff0c;对 Java 应 用程序做性能分析和调优。这些功能包括生成和分析海量数据、跟踪内存泄漏、监控垃圾回…

应用内测分发平台如何上传应用包体?

●您可免费将您的应用&#xff08;支持苹果.ios安卓.apk文件&#xff09;上传至咕噜分发平台&#xff0c;我们将免费为应用生成下载信息&#xff0c;但咕噜分发将会对应用的下载次数进行收费&#xff08;每个账号都享有免费赠送的下载点数以及参加活动的赠送点数&#xff09;&a…

RTMDet原理与代码解析

paper&#xff1a;RTMDet: An Empirical Study of Designing Real-Time Object Detectors official implementation&#xff1a;https://github.com/open-mmlab/mmdetection/tree/main/configs/rtmdet 本文的创新点 Backbone and Neck 在backbone的basic building block中采…

Spark SQL 时间格式处理

初始化Spark Sql package pbcp_2023.clear_dataimport org.apache.spark.SparkConf import org.apache.spark.sql.SparkSession import org.apache.spark.sql.functions.{current_date, current_timestamp}object twe_2 {def main(args: Array[String]): Unit {val con new …

js获取时间日期

目录 Date 对象 1. 获取当前时间 2. 获取特定日期时间 Date 对象的方法 1. 获取各种日期时间组件 2. 获取星期几 3. 获取时间戳 格式化日期时间 1. 使用 toLocaleString() 方法 2. 使用第三方库 UNIX 时间戳 内部表示 时区 Date 对象 JavaScript中内置的 Date 对象…

获取DOM元素和类型判断

一、获取dom元素 <div id"one" class"one">我是第一个div</div> <div>我是第二个div</div> <div name"username">我是第三个div</div> <input type"text" name"username"> 1.qu…

【完美世界】叶倾仙强势登场,孔雀神主VS护道人,石昊重逢清漪

Hello,小伙伴们&#xff0c;我是拾荒君。 《完美世界》国漫第138集已更新。在这一集中&#xff0c;天人族的行为让人大跌眼镜&#xff0c;他们不仅没有对石昊感恩戴德&#xff0c;反而一心想要夺取他身上的所有秘法宝术。天人族的护道人登场&#xff0c;试图以强大的实力压制石…

Theta*: Any-Angle Path Planning on Grids 原文翻译

Theta*: Any-Angle Path Planning on Grids 文章目录 Theta*: Any-Angle Path Planning on Grids翻译摘要1.Introduction2. Path-Planning Problem and Notation3. Existing Terrain Discretizations4.Existing Path-Planning Algorithms4.1 A* on GridsA* with Post-Smoothed …

2023年【R1快开门式压力容器操作】考试资料及R1快开门式压力容器操作复审考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 R1快开门式压力容器操作考试资料参考答案及R1快开门式压力容器操作考试试题解析是安全生产模拟考试一点通题库老师及R1快开门式压力容器操作操作证已考过的学员汇总&#xff0c;相对有效帮助R1快开门式压力容器操作复…