1 set 的概念

set 是 STL 容器的一种,它的底层实现是树(特殊的二叉搜索树),因此,它的增删查效率为 O(log2N),并且 set 还分为了 普通的 set multiset,普通的 set 不会出现重复的值multiset 中则会出现重复的值,因此,它们的部分接口使用起来也会有不一样的效果

如果要使用 set,就需要包含头文件 set,由于容器处于 std 命名空间下,所以需要指定命名空间 std

#include <set>

2 set 的构造方式

在这里插入图片描述
在构造 set 或 multiset 时,要使用以下语法:

set<数据类型,比较规则> 对象名();
multiset<数据类型,比较规则> 对象名();

在这其中,数据类型和对象名必须要给定,但是比较规则不是必须的,如果没有指定比较规则,那么默认使用升序,也就是按照二叉搜索树左小于根小于右的规则来存放数据

构造 set 时,有三种选择:

(1)构造空的 set 或 multiset

对象名后不加括号,就是在构造空的 set

int main()
{
	set<int> s; //空set
	multiset<int> muls;
	return 0;
}

(2)通过另外一个容器的内容来构造 set 或 multiset

构造时,需要给出另一个容器某个范围内的数据

int main()
{
	vector<int> v({ 5,3,1,2,4 });
	set<int> s(v.begin(), v.end()); //用v内的所有数据构造s
	multiset<int> muls(v.begin(), v.end());
	return 0;
}

(3)通过另一个 set 或 multiset 来构造 set 或 multiset(拷贝构造)

int main()
{
	vector<int> v({ 5,3,1,2,4 });
	set<int> s(v.begin(), v.end());
	set<int> s2(s); //拷贝构造

	multiset<int> muls(v.begin(), v.end());
	multiset<int> muls2(muls);
	return 0;
}

3 set 的常用接口

3.1 set 的遍历

由于 set 底层是使用树来实现的,所以它的迭代器是双向迭代器只能++,- -,不能随机访问,因此它也不支持使用下标遍历只支持迭代器和范围 for 遍历

(1) 迭代器遍历

使用迭代器遍历 set 时,要用到以下接口

接口名称 作用 返回值类型
begin() 返回指向第一个元素的迭代器 普通对象返回 iterator,const 对象返回 const_iterator
end() 返回指向最后一个元素的下一个位置的迭代器 普通对象返回 iterator,const 对象返回 const_iterator
rbegin() 返回指向最后一个元素的迭代器 普通对象返回 reverse_iterator,const 对象返回 const_reverse_iterator
rend() 返回指向第一个元素的前一个位置的迭代器 普通对象返回 reverse_iterator,const 对象返回 const_reverse_iterator

正向遍历:

int main()
{
	//遍历set
	set<int> s({ 5,3,1,2,4 });
	set<int>::iterator it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
	//遍历multiset
	multiset<int> muls({ 8,7,4,5,9 });
	set<int>::iterator it2 = muls.begin();
	while (it2 != muls.end())
	{
		cout << *it2 << " ";
		it2++;
	}
	return 0;
}

结果:

1 2 3 4 5
4 5 7 8 9

反向遍历:

int main()
{
	//遍历set
	set<int> s({ 5,3,1,2,4 });
	set<int>::reverse_iterator rit = s.rbegin();
	while (rit != s.rend())
	{
		cout << *rit << " ";
		rit++;
	}
	cout << endl;
	//遍历multiset
	multiset<int> muls({ 8,7,4,5,9 });
	set<int>::reverse_iterator rit2 = muls.rbegin();
	while (rit2 != muls.rend())
	{
		cout << *rit2 << " ";
		rit2++;
	}
	return 0;
}

结果:

5 4 3 2 1
9 8 7 5 4

(2) 范围 for 遍历

int main()
{
	//遍历set
	set<int> s({ 5,3,1,2,4 });
	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl;
	//遍历multiset
	multiset<int> muls({ 8,7,4,5,9 });
	for (auto e : muls)
	{
		cout << e << " ";
	}
	return 0;
}

结果:

1 2 3 4 5
4 5 7 8 9

3.2 set 中与容量相关的接口

3.2.1 empty

empty 的作用是判断 set 是否为空,是空返回 true,不是空则返回 false

int main()
{
	set<int> s1;
	set<int> s2({ 5,3,1,2,4 });
	cout << s1.empty() << endl;
	cout << s2.empty() << endl;

	multiset<int> muls1;
	multiset<int> muls2({ 8,7,4,5,9 });
	cout << muls1.empty() << endl;
	cout << muls2.empty() << endl;
	return 0;
}

结果:

1
0
1
0

3.2.2 size

size 的作用是获取 set 中有效元素的个数

int main()
{
	set<int> s1({ 5,3,1,2,4 });
	cout << s1.size() << endl;

	multiset<int> muls1({ 8,7,4,5,9 });
	cout << muls1.size() << endl;
	return 0;
}

结果:

5
5

3.2.3 max_size

max_size 的作用是获取 set 能存放的数据个数

int main()
{
	set<int> s1({ 5,3,1,2,4 });
	cout << s1.max_size() << endl;

	multiset<int> muls1({ 8,7,4,5,9 });
	cout << muls1.max_size() << endl;
	return 0;
}

结果:

576460752303423487
576460752303423487

3.3 set 的增删查

由于 set 是由红黑树实现的,并且存的只有一个值,不是一个键值对,所以 set 不存在修改操作,如果对 set 中的值进行修改,那么 set 内部的结构就会混乱,就不会是一棵红黑树 (或BST)

3.3.1 insert

在这里插入图片描述

insert 的作用是往 set 集合中插入一个元素,在插入时,会根据给定的比较规则来查找合适的插入位置,插入的方式有三种:

(1)直接插入指定的值

使用这种方式插入 val 时,最终会返回一个键值对,这个键值对内保存了一个迭代器和一个布尔类型的值,它会因为 set 和 multiset 有所不同
普通的 set 中没有重复的值,所以如果原来 set 中就有待插入的值 val,那么返回的迭代器就会指向它,布尔类型的值为 false,如果原来 set 中没有待插入的值 val,那么返回的迭代器也会指向它,布尔类型的值为 true
multiset 中可以有重复的值,所以返回的迭代器会指向 val,布尔类型的值永远为 false

int main()
{
	set<int> s1({ 5,3,1,2,4 });
	//插入不存在的值
	pair<set<int>::iterator, bool> p = s1.insert(6);
	cout << *(p.first) << " " << p.second << endl;
	//插入已存在的值
	pair<set<int>::iterator, bool> p2 = s1.insert(5);
	cout << *(p2.first) << " " << p2.second << endl;

	cout << endl;

	multiset<int> muls1({ 8,7,4,5,9 });
	//插入不存在的值
	pair<set<int>::iterator, bool> p3 = s1.insert(6);
	cout << *(p3.first) << " " << p3.second << endl;
	//插入已存在的值
	pair<set<int>::iterator, bool> p4 = s1.insert(5);
	cout << *(p4.first) << " " << p4.second << endl;

	return 0;
}

结果:

6 1
5 0

6 0
5 0

(2)在指定的位置插入指定的值

以这种方式插入值 val 时,会返回指向它的迭代器,虽然是在指定位置插入值,但是插入时仍然会遵守二叉搜索树的规则先查找再插入,保证有序

int main()
{
	set<int> s1({ 5,3,1,2,4 });
	set<int>::iterator it1 = s1.insert(s1.begin(), 6);
	cout << *it1 << endl;
	for (auto e : s1)
	{
		cout << e << " ";
	}

	cout << endl;

	multiset<int> muls1({ 8,7,4,5,9 });
	set<int>::iterator it2 = muls1.insert(muls1.begin(), 6);
	cout << *it2 << endl;
	for (auto e : muls1)
	{
		cout << e << " ";
	}

	return 0;
}

结果:

6
1 2 3 4 5 6
6
4 5 6 7 8 9

(3)插入指定区间内的值

int main()
{
	vector<int> v({ 5,3,1,2,4 });
	set<int> s1;
	s1.insert(v.begin(), v.end());
	for (auto e : s1)
	{
		cout << e << " ";
	}

	cout << endl;

	multiset<int> muls1;
	muls1.insert(v.begin() + 1, v.end() - 1);
	for (auto e : muls1)
	{
		cout << e << " ";
	}

	return 0;
}

结果:

1 2 3 4 5
1 2 3

3.3.2 erase

在这里插入图片描述

erase 的作用是删除 set 中的一个指定元素,删除的方式有三种:

(1)删除指定位置上的值

int main()
{
	set<int> s1({ 1,2,3,4,5 });
	s1.erase(s1.begin()); //删除1
	for (auto e : s1)
	{
		cout << e << " ";
	}

	cout << endl;

	multiset<int> muls1({ 4,4,5,5,6,6 });
	muls1.erase(muls1.begin()); //删除4
	for (auto e : muls1)
	{
		cout << e << " ";
	}

	return 0;
}

结果:

2 3 4 5
5 7 8 9

(2)删除指定的值

用这种方式删除普通 set 中的数据时,只会返回 0 或 1,因为普通 set 中数据不会重复,要么有 1 个要么就是没有
但是删除 multiset 中的数据时,会将所有相等的数据删除返回删除的数据个数,因为 multiset 中允许数据重复

int main()
{
	set<int> s1({ 1,2,3,4,5 });
	cout << s1.erase(1) << endl; //删除1
	for (auto e : s1)
	{
		cout << e << " ";
	}

	cout << endl;

	multiset<int> muls1({ 4,4,5,5,6,6 });
	cout << muls1.erase(4) << endl; //删除4
	for (auto e : muls1)
	{
		cout << e << " ";
	}

	return 0;
}

结果:

1
2 3 4 5
2
5 5 6 6

(3)删除指定区间上的值

int main()
{
	set<int> s1({ 1,2,3,4,5 });
	s1.erase(++s1.begin(), --s1.end()); //删除2~4
	for (auto e : s1)
	{
		cout << e << " ";
	}

	cout << endl;

	multiset<int> muls1({ 4,4,5,5,6,6 });
	muls1.erase(++muls1.begin(), --muls1.end()); //删除第二个4~第一个6
	for (auto e : muls1)
	{
		cout << e << " ";
	}

	return 0;
}

结果:

1 5
4 6

3.3.3 find

find 的作用是在 set 中查找指定的值,返回它的迭代器

int main()
{
	set<int> s1({ 1,2,3,4,5 });
	set<int>::iterator it = s1.find(2); //查找2
	cout << *it << endl;

	cout << endl;

	multiset<int> muls1({ 4,4,5,5,6,6 });
	multiset<int>::iterator it2 = muls1.find(5); //查找5
	cout << *it2 << endl;
	return 0;
}

结果:

2

5

3.3.4 swap

swap 的作用是交换两个 set 中的值

int main()
{
	set<int> s1({ 1,2,3,4,5 });
	set<int> s2({ 6,7,8,9,10 });
	cout << "s1 before swap: " << endl;
	for (auto e : s1)
	{
		cout << e << " ";
	}
	cout << endl;
	cout << "s2 before swap: " << endl;
	for (auto e : s2)
	{
		cout << e << " ";
	}

	s1.swap(s2); //交换s1,s2
	cout << endl;

	cout << "s1 after swap: " << endl;
	for (auto e : s1)
	{
		cout << e << " ";
	}
	cout << endl;
	cout << "s2 after swap: " << endl;
	for (auto e : s2)
	{
		cout << e << " ";
	}

	cout << endl;
	cout << endl;

	multiset<int> muls1({ 4,4,5,5,6,6 });
	multiset<int> muls2({ 7,7,8,8,9,9 });
	cout << "muls1 before swap: " << endl;
	for (auto e : muls1)
	{
		cout << e << " ";
	}
	cout << endl;
	cout << "muls2 before swap: " << endl;
	for (auto e : muls2)
	{
		cout << e << " ";
	}

	muls1.swap(muls2); //交换muls1,muls2
	cout << endl;

	cout << "muls1 after swap: " << endl;
	for (auto e : muls1)
	{
		cout << e << " ";
	}
	cout << endl;
	cout << "muls2 after swap: " << endl;
	for (auto e : muls2)
	{
		cout << e << " ";
	}

	return 0;
}

结果:

s1 before swap:
1 2 3 4 5
s2 before swap:
6 7 8 9 10
s1 after swap:
6 7 8 9 10
s2 after swap:
1 2 3 4 5

muls1 before swap:
4 4 5 5 6 6
muls2 before swap:
7 7 8 8 9 9
muls1 after swap:
7 7 8 8 9 9
muls2 after swap:
4 4 5 5 6 6

3.3.5 clear

clear 的作用是将 set 清空

int main()
{
	set<int> s1({ 1,2,3,4,5 });
	s1.clear();
	cout << s1.empty() << endl;

	cout << endl;

	multiset<int> muls1({ 4,4,5,5,6,6 });
	muls1.clear();
	cout << muls1.empty() << endl;
	return 0;
}

结果:

1

1

3.4 set 的其它操作

3.4.1 lower_bound 和 upper_bound

lower_bound的作用是查找出一个指定的值,返回它的迭代器,如果有多个相同的,则返回中序的第一个
upper_bound的作用是查找出大于指定值的第一个值,返回它的迭代器
一般它们会结合使用,来确定左闭右开的区间

int main()
{
	set<int> s1({ 10,20,30,40,50,60 });
	set<int>::iterator lowerIt = s1.lower_bound(20); //查找20
	cout << *lowerIt << endl;
	set<int>::iterator upperIt = s1.upper_bound(40); //查找大于40的第一个数
	cout << *upperIt << endl;
	set<int> s2(lowerIt, upperIt);
	//[20,30,40,50)
	for (auto e : s2)
	{
		cout << e << " ";
	}

	cout << endl;
	cout << endl;

	multiset<int> muls1({ 10,10,20,20,30,30,40,40 });
	multiset<int>::iterator lowerIt2 = muls1.lower_bound(20); //查找第一个出现的20
	cout << *lowerIt2 << endl;
	multiset<int>::iterator upperIt2 = muls1.upper_bound(30); //查找大于30的第一个数
	cout << *upperIt2 << endl;
	multiset<int> muls2(lowerIt2, upperIt2);
	//[20,20,30,30,40)
	for (auto e : muls2)
	{
		cout << e << " ";
	}
	return 0;
}

结果:

20
50
20 30 40

20
40
20 20 30 30

3.4.2 count

count 的作用是计算 set 中指定值的个数,对于普通的 set 来说,只会返回 0 或 1,因为值不会重复,要么有一个值要么没有值,对于 multiset 来说,则有可能会返回各个正整数,因为 multiset 中值会重复

int main()
{
	set<int> s1({ 1,2,3,4,5 });
	cout << s1.count(2) << endl;
	cout << s1.count(6) << endl;
	
	cout << endl;

	multiset<int> muls1({ 4,4,5,5,6,6 });
	cout << muls1.count(4) << endl;
	cout << muls1.count(7) << endl;

	return 0;
}

结果:

1
0

2
0

3.4.3 equal_range

equal_range 的作用是返回相等的值的区间,该区间是左闭右开的,返回值是一个键值对,相当于用两个迭代器确定了一个区间。这个接口对于 set 来说意义不大,因为 set 中值不会重复,但是对于 multiset 来说是有意义的,因为 multiset 的值会重复

int main()
{
	multiset<int> muls1({ 4,4,4,4,7,9,10,11 });
	pair<multiset<int>::iterator, multiset<int>::iterator> p = muls1.equal_range(4);

	multiset<int> muls2(p.first, p.second); //使用区间构造multiset
	for (auto e : muls2)
	{
		cout << e << " ";
	}

	return 0;
}

结果:

4 4 4 4

更多推荐