c++的STL(5)-- set和multiset容器

set和multiset容器概述

  •  首先set和multiset这两种容器底层是红黑树(是一种数据结构,是树形结构)实现的。
  • 我们前面说到vector,deque容器插入数据慢,但是读取数据快list呢恰恰相反,插入数据快,查询慢。但是set和multiset数据查询和插入速度都很快(由于底层是有红黑树实现)。
  • 对于set我们依然没有办法使用下标[]或者at(),来访问数据,而且其迭代器和指针也只能进行++,--运算(前置和后置都行),!=,==的比较运算。(这个和list容器是类似的,因为在set中元素和元素之间也是通过指针来联系的,它们的内存也不挨着 -- 所以set和multiset也不需要提前开辟空间)
  • set容器中存放的元素不能有重复的,但是multiset是可以存放相同元素的
  • set和multiset会对数据进行自动排序(我们可以指定排序规则)。
表 C++ set 容器常用成员方法
成员方法功能
begin()返回指向容器中第一个(注意,是已排好序的第一个)元素的双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
end()返回指向容器最后一个元素(注意,是已排好序的最后一个)所在位置后一个位置的双向迭代器,通常和 begin() 结合使用。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
rbegin()返回指向最后一个(注意,是已排好序的最后一个)元素的反向双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。
rend()返回指向第一个(注意,是已排好序的第一个)元素所在位置前一个位置的反向双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。
cbegin()和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
cend()和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
crbegin()和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
crend()和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
find(val)在 set 容器中查找值为 val 的元素,如果成功找到,则返回指向该元素的双向迭代器;反之,则返回和 end() 方法一样的迭代器。另外,如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
lower_bound(val)返回一个指向当前 set 容器中第一个大于或等于 val 的元素的双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
upper_bound(val)返回一个指向当前 set 容器中第一个大于 val 的元素的迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
equal_range(val)该方法返回一个 pair 对象(包含 2 个双向迭代器),其中 pair.first 和 lower_bound() 方法的返回值等价,pair.second 和 upper_bound() 方法的返回值等价。也就是说,该方法将返回一个范围,该范围中包含的值为 val 的元素(set 容器中各个元素是唯一的,因此该范围最多包含一个元素)。
empty()若容器为空,则返回 true;否则 false。
size()返回当前 set 容器中存有元素的个数。
max_size()返回 set 容器所能容纳元素的最大个数,不同的操作系统,其返回值亦不相同。
insert()向 set 容器中插入元素。
erase()删除 set 容器中存储的元素。
swap()交换 2 个 set 容器中存储的所有元素。这意味着,操作的 2 个 set 容器的类型必须相同。
clear()清空 set 容器中所有的元素,即令 set 容器的 size() 为 0。
emplace()在当前 set 容器中的指定位置直接构造新元素。其效果和 insert() 一样,但效率更高。
emplace_hint()在本质上和 emplace() 在 set 容器中构造新元素的方式是一样的,不同之处在于,使用者必须为该方法提供一个指示新元素生成位置的迭代器,并作为该方法的第一个参数。
count(val)在当前 set 容器中,查找值为 val 的元素的个数,并返回。注意,由于 set 容器中各元素的值是唯一的,因此该函数的返回值最大为 1。

先说set容器再说multiset容器,首先使用set或者multiset都需要导入头文件#include<set> 

1. set的默认构造 

代码 

set<int> s1;
set<float> s2;
set<Student> s3;
set<int *> s4;
set<Student *> s5;

2. set的有参构造 

代码 

	/*
		vector<int> v1{10,11,12,13,14,15,16};
		int arr[] = { 100,200,300,400,500 };

		set<int> s1{ 1,2,3,4,5 };
		set<int> s2(s1);
		set<int> s3(s1.begin(),s1.begin()++);
		set<int> s4(v1.begin(), v1.end());
		set<int> s5(arr, arr + 3);
	*/
相关知识点 
  • 和之前的容器没有什么区别。(set不能使用数字直接作为有参构造的参数) 

 3. set容器的元素个数

代码:   使用size()函数

int main(void) {
	set<int> s1{10,11,12,13,14,15};

	cout << "set 的元素个数: " << s1.size() << endl;  // 6

	system("pause");
	return 0;
}

代码:  使用count()函数 

int main(void) {
	set<int> s1{10,11,12,13,14,15};

	set<int>::size_type nub = s1.count(10);  // 或者使用size_t

	cout << "10出现的个数:" << nub << endl;

	system("pause");
	return 0;
}

结果 

相关知识点 
  •  count(elem);  // 返回容器中元素elem的个数为多少?
  • 注意:  因为set中存储的各个元素不能相同,所以count只能返回0或者1。对于multiset可以大于1

4. set的元素访问(有变动)

代码:  使用迭代器

int main(void) {
	set<int> s1{10,11,12,13,14,15};

	for (set<int>::iterator it = s1.begin(); it != s1.end(); it++) {
		cout << *it << " ";    // 10 11 12 13 14 15
	}

	cout << endl;

	system("pause");
	return 0;
}
相关知识点 
  • set无法使用下标进行访问元素,所以无法使用[]和()进行访问。
  • set没有函数front()和back()函数。(可以这么理解,树形结构没有直观的先后顺序)
  • 所以set只能使用迭代器访问元素。

5. set的迭代器 

表  C++ set 容器迭代器方法
成员方法功能
begin()返回指向容器中第一个(注意,是已排好序的第一个)元素的双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
end()返回指向容器最后一个元素(注意,是已排好序的最后一个)所在位置后一个位置的双向迭代器,通常和 begin() 结合使用。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
rbegin()返回指向最后一个(注意,是已排好序的最后一个)元素的反向双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。
rend()返回指向第一个(注意,是已排好序的第一个)元素所在位置前一个位置的反向双向迭代器。通常和 rbegin() 结合使用。如果 set 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。
cbegin()和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
cend()和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
crbegin()和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
crend()和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
find(val)在 set 容器中查找值为 val 的元素,如果成功找到,则返回指向该元素的双向迭代器;反之,则返回和 end() 方法一样的迭代器。另外,如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
lower_bound(val)返回一个指向当前 set 容器中第一个大于或等于 val 的元素的双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
upper_bound(val)返回一个指向当前 set 容器中第一个大于 val 的元素的迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
equal_range(val)该方法返回一个 pair 对象(包含 2 个双向迭代器),其中 pair.first 和 lower_bound() 方法的返回值等价,pair.second 和 upper_bound() 方法的返回值等价。也就是说,该方法将返回一个范围,该范围中包含的值为 val 的元素(set 容器中各个元素是唯一的,因此该范围最多包含一个元素)。

注意,以上成员函数返回的迭代器,指向的只是 set 容器中存储的元素,而不再是键值对。另外,以上成员方法返回的迭代器,无论是 const 类型还是非 const 类型,都不能用于修改 set 容器中的值。

 代码

int main(void) {
	set<int> s1{10,11,12,13,14,15};

	for (set<int>::iterator it = s1.begin(); it != s1.end(); it++) {
		cout << *it << " ";    // 10 11 12 13 14 15
		//   *it = 5;          //错误操作,不能直接修改set中的元素值
	}

	system("pause");
	return 0;
}
相关知识点 

注意:   set的迭代器只能进行++,--(前置或者后置)的运算操作,==,!=的比较运算。 以及不能使用迭代器修改set中的元素值。(无论迭代器有没有const修饰)

其余操作和前面介绍的容器一样。

6. set在指定位置插入元素 

代码:使用insert()函数

int main(void) {
	set<int> s1{ 10,11,12,13,14,15 };
	set<int> s2{ 19,5 };
	vector<int> v1{ 99,100 };
	int arr[] = { 1,2,3 };

	s1.insert(s2.begin(), s2.end());
	s1.insert(arr, arr+3);
	s1.insert(v1.begin(), v1.end());
	s1.insert(4);
	// s1.insert(4, 6);  // 这样写会出错,因为set中不能有重复的元素
	s1.insert({ 6 });
	s1.insert(s1.begin(), 50);
  

	for (set<int>::iterator it = s1.begin(); it != s1.end(); it++) {
		cout << *it << " ";    // 10 11 12 13 14 15
	}

	system("pause");
	return 0;
}

结果 

相关知识点 
  • insert(elem);  // 将元素elem插入到set中去
  • insert(ptr1,ptr2) ;  // 将[ptr1,ptr2)指针指向的范围中的数据插入到set中去。(数组)
  • insert(beg,end);   // 将迭代器[beg,end)范围内的数据插入到set中去。(可以相同容器的迭代器,也可以是不同容器的)
  • insert({data...});  //  将{}中的元素插入到set中
  • insert(iter,elem);  // 将元素elem插入到set中

注意:   无论是哪种插入,在元素插入到set中时,set都会进行比较,放到合适的位置。因为set是有序的。(尽管我们指定了插入位置,但数据并不一定会放到对应位置,它会根据排序规则来确定其放在哪个位置)

insert的返回值 

情况一:
set<int> s1{1,2,3};

pair<set<int>::iterator,bool> p1 = s1.insert(5);
if (p1.second) {
	cout << *(p1.first) << endl;
}

pair<set<int>::iterator, bool>p2 = s1.insert(5);
if (!p2.second) {
	cout << "插入失败" << endl;
	cout << *(p2.first) << endl;
}
相关知识点: 

 当我们直接插入单个数据的时候,对于set会存在插入失败的情况,因为set中的元素不能重复,如果我们插入的元素在set中已经存在就会插入失败。

那么我们怎么能判断是否插入成功了呢?  

insert会给我们返回一个bool值,如果插入成功返回true,如果插入失败返回false

同时insert还会返回一个迭代器,如果插入成功迭代器指向插入元素的位置,如果插入失败迭代器指向与插入值相同的元素位置

上面代码中,我们分别展示了插入成功和插入失败的情况。

其它情况 
set<int> s1;
set<int> s2{1,2,3};

s1.insert(s2.begin(), s2.end());  // 返回void

set<int>::iterator it = s1.insert(s1.begin(),5);  // 返回指向插入元素位置的迭代器
cout << *it << endl;

s1.insert({ 10,11 });  // 返回void
  • 在指定位置插入时 (但是,由于set是有序的,所以插入的数据会根据内部数据进行位置调整,不一定会放到我们指定的位置)。
  • 如果插入成功: 返回插入元素位置的迭代器;如果插入失败: 返回与插入值相同的元素位置。 

代码:  使用emplace()和emplace_hint()函数

set<int> s1;

//  使用emplace
pair<set<int>::iterator,bool> p1 = s1.emplace();   // 插入0,返回pair类对象
if (p1.second) {
	cout << *(p1.first) << endl;
}

pair<set<int>::iterator, bool> p2 = s1.emplace(1);  // 插入1,返回pair类对象
if (p2.second) {
	cout << *(p2.first) << endl;
}

// 使用emplace_hint
set<int>::iterator it = s1.emplace_hint(s1.begin(), 10);   // 在指定位置插入10,返回指向插入元素位置的迭代器
cout << *it << endl;

结果 

相关知识点 
  •  emplace()函数和之前介绍的用法是一致的,直接在()写相应的数据(类对象的话,就写相应的构造函数参数对应的数据。其它类型的话,直接写对应的数据)

其返回值是一个pair的类对象,和insert是一样的

返回一个迭代器 -> 如果插入成功:  指向插入数据的位置; 如果插入失败:  指向和插入数据相同的元素位置。

返回bool值 ->  如果插入成功:  返回true;  如果插入失败:  返回false。

  • emplace_hint(pos,elem);  //  在pos位置插入数据elem。  (但是,由于set是有序的,所以插入的数据会根据内部数据进行位置调整,不一定会放到我们指定的位置)

其返回值是一个迭代器,如果插入成功:  指向插入数据的位置; 如果插入失败:  指向和插入数据相同的元素位置。

 7. set删除元素

代码:  使用erase()和clear() 

int main(void) {
	set<int> s1{10,11,12,13,14,15};
  
	// 用法1
	set<int>::size_type t1 = s1.erase(10);
	cout << t1 << endl;
	// 用法2
	s1.erase(s1.begin(), ++s1.begin());
	// 用法3
	s1.erase(s1.begin());

	s1.clear();  // 清空set中的元素
	for (set<int>::iterator it = s1.begin(); it != s1.end(); it++) {
		cout << *it << " ";
	}
	cout << endl;

	system("pause");
	return 0;
}
相关知识点 
  • erase(elem);  // 删除set列表中值为elem的元素,返回删除元素的个数。(对于set只会返回0或者1,但是multiset可能会大于1)
  • erase(pos);  // 删除对应位置中的元素, 返回删除元素下一个元素的迭代器。
  • erase(beg,end); // 删除[beg,end)范围内的元素,返回删除元素下一个元素的迭代器。
  • clear(); // 删除set容器中所有的元素。

注意:  在erase和循环结合使用的时候,应该注意删除时迭代器的指向。(在vector中详述)

8. set查找元素 

代码:  使用find()函数 

int main(void) {
	set<int> s1{10,11,12,13,14,15};
	//set<int>::const_iterator cit = s1.find(10);
	set<int>::iterator it = s1.find(10);

	if (it != s1.end()) {
		cout << *it << endl;
	}
	else {
		cout << "没有找到此元素" << endl;
	}

	system("pause");
	return 0;
}

结果 

相关知识点 
  • find(elem);  // 用于查找set中elem元素。
  • 如果此元素存在,则返回该元素位置的迭代器;如果此元素不存在,则返回最后一个元素下一位的迭代器。(和end()函数返回的迭代器一样)

所以在代码中,我们先判断find返回的迭代器和end()返回的迭代器是否相同,相同则输出元素值不相同则输出查找失败。 

代码:  lower_bound(),upper_bound(),equal_range()函数 

int main(void) {
	set<int> s1{10,11,12,13,16,17};

	// lower_bound()
	//set<int>::const_iterator it = s1.lower_bound(10);
	/*
      set<int>::iterator it = s1.lower_bound(10);
	  if (it != s1.end()) {
		cout << *it << endl; // 10
	  }
	  else {
		cout << "没有此元素" << endl;
	  }	
	*/
	set<int>::iterator it = s1.lower_bound(16);
	if (it != s1.end()) {
		cout << *it << endl; // 16
	}
	else {
		cout << "没有此元素" << endl;
	}

	// upper_bound()
	set<int>::iterator it1 = s1.upper_bound(12);
	if (it1 != s1.end()) {
		cout << *it1 << endl; // 13
	}
	else {
		cout << "没有此元素" << endl;
	}

	// equal_range()
	pair<set<int>::iterator, set<int>::iterator> p1 = s1.equal_range(10);
	for (; p1.first != p1.second; (p1.first)++) {
		cout << *(p1.first) << endl;
	}

	system("pause");
	return 0;
}

结果 

相关知识点 
  • lower_bound(elem);  // 返回set中元素>=elem的第一个元素的迭代器,如果set中没有比elem>=的元素,那么就返回最后一个元素下一个位置的迭代器。(和end()返回的一样)
  • upper_bound(elem);  // 返回set中元素>elem的第一个元素的迭代器,如果set中没有比elem>的元素,那么就返回最后一个元素下一个位置的迭代器。(和end()返回的一样)
  • equal_range(elem);  // 返回一个pair对象,内部有两个迭代器类型,第一个迭代器:  表示第一个等于elem元素的位置,第二个迭代器:  表示最后一个等于elem的下一个元素的位置。(set中因为元素不相同,所以这个范围最多包含一个元素)
  • 如果elem不存在,那么两个迭代器就都指向按照规则elem的下一个元素,(如果从小到大排列就指向第一个elem的元素)。 当然如果按照对则,elem排在最后,则两个迭代器都指向最后一个元素的下一位。(比如:  从小到大排时,elem比set中的元素都要大)

举个例子,lower_bound(elem); 要找到是>=elem的数据(并且排序是从小到大排),如果数据为elem的值不存在,那么返回的两个迭代器,就会指向第一个键值>elem的元素。

9.  set的排序规则 

我们前面说到,set是有顺序的,而且在我么插入元素,查找元素等操作中,都会根据排序规则进行相应的调整。

那set的排序规则到底是怎么样的呢? 

set内部使用的是类模板,我们定义set对象的时候,会传入一个类型参数。 其实set还有第二个类型参数,用于指定set的排序规则。(类型为什么可以指定排序规则,这是因为类内部存在一个()函数的重载,没错就是函数对象)

那为什么上面的代码中我们不需要传入第二个类型参数呢?

因为第二个类型参数,有一个默认值为less<类型>,就是内部定义的函数对象类less,其指定的规则是从小到大排序。 所以在默认情况下set是以从小到大的情况进行排序的。

当然,我们也可以自己指定排序规则

代码: 使用greater使set从大到小和自定义的函数对象排列数据

int main(void) {
	set<int> s1{10,11,12,13,16,17};

	cout << "默认情况下输出: " << endl;
	for (set<int>::iterator it = s1.begin(); it != s1.end(); it++) {
		cout << *it << " ";
	}
	cout << endl;

	set<int,greater<int>> s2{ 10,11,12,13,16,17 };
	cout << "greater规则下输出: " << endl;
	for (set<int>::iterator it = s2.begin(); it != s2.end(); it++) {
		cout << *it << " ";
	}
	cout << endl;

	system("pause");
	return 0;
}

结果 

相关知识点 

代码中,我们分别使用了默认情况(less -- 从小到大排序),greater --  从大到小排序。它们打印出的顺序或者元素是不相同的。

 10. set的其它方法

方法:  swap(), empty(), max_size();  

和vector中的使用方法一样。 

11. multiset与set的函数不同之处

multiset和set的函数大多数在使用的时候都是一样的,我们就不再说明了。

但是由于multiset可以存放不同的元素所以和set会有点区别。 

区别:

  • multiset在调用erase(elem),方法时期返回值可以大于1。
  • 同理count(elem)这个函数的返回值也可以大于1。
  • lower_bound(elem)、upper_bound(elem)、equal_range(elem)其对应查找的数据范围也不再是一个,所以这些函数更加常用于multiset。

注意事项: 

  • 由于set内部是有序的,所以我们不能随便修改其内部的值,因为如果修改了某一位置的值,很可能会导致其不再是有序排列的了。所以使用迭代器(即使迭代器不是const修饰的)或者其它方式都不能对set中的元素进行修改。如果要修改,那必须将这个元素删除,然后再添加一个新元素。
  • size_type和value_type和vector的用法一样。(其它内嵌类型也一样)
  • multiset是类似的。

注意,虽然 multiset 容器和 set 容器拥有的成员方法完全相同,但由于 multiset 容器允许存储多个值相同的元素,因此诸如 count()、find()、lower_bound()、upper_bound()、equal_range()等方法,更常用于 multiset 容器。

  • 10
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值