【C++修仙录03】进阶篇:set 使用

嗨~大家好,这里是春栀怡铃声的博客~

"做你害怕的事,然后发现,不过如此~"

目录

set系列的使用

[set 类的介绍](#set 类的介绍)

set的构造和迭代器

set的增删查

单个数据插入,如果已经存在则插入失败

列表插入

迭代器区间插入,已经在容器中存在的值不会插入

删除最小值

算法库的查找

自身查找

[利用count 快速查找](#利用count 快速查找)

lower_bound(val):寻找"下界"

upper_bound(val):寻找"上界"

insert和迭代器遍历使用样例

[find 和 erase使用样例](#find 和 erase使用样例)

算法库的查找

自身查找

[利用count 快速查找](#利用count 快速查找)

直接删除x

直接查找在利用迭代器删除x

[multiset 和 set 的差异](#multiset 和 set 的差异)


我们之前学习的string ,vector,list,deque,array 等,这些容器统称为序列式容器,因为逻辑结构为线性数列的数据结构,2个位置之间储存的值之间一般没有紧密的关联关系。顺序容器中的元素是按他们在容器中的存储位置来顺序保存和访问的。

关联式容器也是用来储存数据的,与序列式容器不同的是,关联式容器逻辑结构通常是非线性结构,俩个位置有紧密的关联关系,交换一下,他的存储关系就破坏了。顺序容器中的元素是按关键字来保存和访问的。关联式容器有 map / set 系列和 unordered_map / unordered_set 系列

set系列的使用

set 类的介绍

set的声明如下:

cpp 复制代码
template < class T,                        // set::key_type/value_type
           class Compare = less<T>,        // set::key_compare/value_compare
           class Alloc = allocator<T>      // set::allocator_type
           > class set;

1.T 是set 底层关键字的类型。

2.set 默认要求是支持小于比较,也就是从小到大排,想按自己的需求走,可以自己是西安仿函数传给第二个模板参数

3.set 底层储存的数据的内存是从空间配置器申请的,如果需要可以自己实现内存池,传给低三个参数

一般情况下,我们都不需要传后两个模版参数。

set底层是用红黑树(平衡二叉搜索树)实现,增删查效率是 O(logN) ,迭代器遍历是走的搜索树的中序,所以是有序的。

set的构造和迭代器

1.无参默认构造

cpp 复制代码
    std::set<int> s_default; 
    // 此时 s_default 为空,后续可以通过 insert 动态添加元素
    s_default.insert(10);
    s_default.insert(30);
    s_default.insert(20);
  1. 列表构造 - C++11 及以上
cpp 复制代码
// 直接使用花括号 {} 给出初始元素,会自动去重(去掉重复的 3)并排序
std::set<int> s_list = {5, 3, 8, 1, 3}; 
  1. 迭代器区间构造
cpp 复制代码
std::vector<int> vec = {100, 400, 200, 300, 200};
// 传入另一个容器的常用区间 [begin, end) 来构造 set
std::set<int> s_range(vec.begin(), vec.end());

4.拷贝构造

cpp 复制代码
 // 使用一个已存在的 set 对象来初始化一个新的 set 对象
 std::set<int> s_copy(s_list);

set的迭代器是双向迭代器(只能进行++、-- )支持迭代器意味着支持范围for

set的 iterator 和 const_iterator 都不支持迭代器修改数据,修改关键字数据,破坏了底层搜索树的结构。

cpp 复制代码
// 正向迭代器 
iterator begin();
iterator end();
// 反向迭代器 
reverse_iterator rbegin();
reverse_iterator rend();

set的增删查

单个数据插入,如果已经存在则插入失败
cpp 复制代码
int main()
{
    set<int>s;
	s.insert(6);
	s.insert(5);
	s.insert(7);
	s.insert(3);
	s.insert(6);
    return 0;
}
列表插入

(它允许你使用一对花括号 {} 组织多个元素,并一次性将它们插入到 set 中。),

已经在容器中存在的值不会插入

cpp 复制代码
set<int> s;
s.insert({2,8,9,3,2});
迭代器区间插入,已经在容器中存在的值不会插入
cpp 复制代码
    std::set<int> s;

    // --- 场景 1:从 vector 中插入 ---
    std::vector<int> vec = {5, 2, 8, 5, 2, 9, 1};
    
    // 将 vec 的所有元素插入到 set 中
    // 注意:区间是 [vec.begin(), vec.end())
    s.insert(vec.begin(), vec.end());
删除最小值
cpp 复制代码
set<int> s = { 4,2,7,2,8,5,9 };

	//删除最小值
	s.erase(s.begin());
算法库的查找
cpp 复制代码
   set<int> s = { 4,2,7,2,8,5,9 };
   int x;
   cin >> x;
   auto pos1 = find(s.begin(), s.end(), x);

利用算法库的find函数,借助迭代器遍历整个s ,寻找目标值

自身查找
cpp 复制代码
set<int> s = { 4,2,7,2,8,5,9 };
auto pos2 = s.find(x);

利用set 自身实现的find 函数寻找目标值

利用count 快速查找
cpp 复制代码
set<int> s = { 4,2,7,2,8,5,9 };
if (s.count(x))
    {
        cout << x << "在" << endl;
    }
    else
    {
        cout << x << "不存在" << endl;
    }

如果 s 中有目标值,s.count(x) 的返回值是1,否则返回0

lower_bound(val):寻找"下界"

  • 作用: 返回一个迭代器,指向 set第一个大于或等于 (>=) val 的元素。

  • 理解: 它是寻找"刚好不小于目标值"的位置。

    • 如果 valset 中存在,它就指向 val 本身。

    • 如果 val 不存在,它就指向比 val 大的下一个元素。

    • 如果 valset 中的所有元素都大,它会返回 set::end()(指向集合末尾的越界迭代器)。

upper_bound(val):寻找"上界"

  • 作用: 返回一个迭代器,指向 set第一个严格大于 (>) val 的元素。

  • 理解: 它总是寻找"刚好比目标值大一点"的位置,绝对不包含目标值本身

    • 无论 val 在不在 set 中,它找的都是下一个比 val 大的元素。

    • 同样,如果找不到,就返回 set::end()

    • 例子:删除25,55区间所有的值

cpp 复制代码
int main()
{
	set<int> myset;
	for (int i = 1; i < 10; i++)
		myset.insert(i * 10);
	for (auto e : myset)
    {
		cout << e << " ";
	}
	    cout << endl;

	 //	// [30, 50] 值
	//	// [25, 55] 值
		auto itlow = myset.lower_bound(25);
		auto itup = myset.upper_bound(55);
		myset.erase(itlow, itup);
		for (auto e : myset)
		{
			cout << e << " ";
		}
		cout << endl;

	return 0;
}

insert和迭代器遍历使用样例

cpp 复制代码
int main()
{
	set<int>s;
	s.insert(6);
	s.insert(5);
	s.insert(7);
	s.insert(3);
	s.insert(6);

	set<int>::iterator it = s.begin();

	while (it != s.end())
	{
		//*it = 1;
		cout << *it << " ";
		++it;
	}
	cout << endl;

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


	return 0;
}

find 和 erase使用样例

算法库的查找
cpp 复制代码
   set<int> s = { 4,2,7,2,8,5,9 };
   int x;
   cin >> x;
   auto pos1 = find(s.begin(), s.end(), x);

利用算法库的find函数,借助迭代器遍历整个s ,寻找目标值

自身查找
cpp 复制代码
set<int> s = { 4,2,7,2,8,5,9 };
auto pos2 = s.find(x);

利用set 自身实现的find 函数寻找目标值

利用count 快速查找
cpp 复制代码
set<int> s = { 4,2,7,2,8,5,9 };
if (s.count(x))
    {
        cout << x << "在" << endl;
    }
    else
    {
        cout << x << "不存在" << endl;
    }

如果 s 中有目标值,s.count(x) 的返回值是1,否则返回0

直接删除x
cpp 复制代码
int x;
cin >> x;
int num = s.erase(x);
if (num == 0)
{
   cout << x << "不存在!" << endl;
}
for (auto e : s)
{
   cout << e << " ";
}
 cout << endl;
直接查找在利用迭代器删除x
cpp 复制代码
cin >> x;
auto pos = s.find(x);
if (pos != s.end())
{
  s.erase(pos);
}
else
{
  cout << x << "不存在!" << endl;
}
for (auto e : s)
{
  cout << e << " ";
}
 cout << endl;
  • cin >> x;

    • 作用 :从标准输入(键盘)读取一个数值,存入变量 x 中。也就是说,由用户来决定接下来要删除哪个数字。
  • auto pos = s.find(x);

    • 作用 :在集合 s 中查找值为 x 的元素。

    • 机制 :如果找到了,它会返回一个指向该元素的迭代器 (类似于指针);如果没找到,它会返回集合的末尾标记 s.end()

    • 关键细节(针对 multiset :如果集合中有多个相同的 x(例如有四个 4),find 函数只会返回第一个匹配元素的迭代器

  • if (pos != s.end()) { s.erase(pos); }

    • 作用 :判断是否找到了元素。如果 pos 不等于 s.end(),说明查找成功,执行删除。

    • 核心精髓 :调用 s.erase(pos) 传递的是迭代器 。它只会删除该迭代器所精准指向的那一个位置上的元素

    • 重要对比 :如果集合里有四个 4,这段代码执行完后只会删掉一个 4,还剩三个 4 。这与直接调用 s.erase(x)(传入具体数值,会把所有等于 x 的元素赶尽杀绝)有着本质的区别。

  • else { cout << x << "不存在!" << endl; }

    • 作用 :如果 pos == s.end(),说明集合里压根没有数字 x,程序会输出提示信息,告诉用户该元素不存在。
  • for (auto e : s) { cout << e << " "; } cout << endl;

    • 作用 :使用 C++11 的基于范围的 for 循环(Range-based for loop) ,从头到尾遍历此时集合 s 中的每一个元素并打印出来,元素之间用空格隔开,最后换行。

    • 特性 :因为 set/multiset 底层是红黑树,内部时刻保持有序,所以打印出来的结果一定是从小到大排列的。

multiset 和 set 的差异

相比于set ,multiset 是排序,但是不去重

相比set不同的是,x可能会存在多个,find查找中序的第一个

相比set不同的是,count会返回x的实际个数

相比set不同的是,erase给值时会删除所有的x

cpp 复制代码
int main()
{
	//multiset 不去重
	multiset<int> s = { 4,2,7,2,4,8,4,5,4,9 };
	auto it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;

	int x;
	cin >> x;
	auto pos = s.find(x);// 相⽐set不同的是,x可能会存在多个,find查找中序的第⼀个
	while (pos != s.end() && *pos == x)
	{
		cout << *pos << " ";
		++pos;
	}
	cout << endl;
	// 相比set不同的是,count会返回x的实际个数
	cout << s.count(x) << endl;


	s.erase(x);
	it = s.begin();
		while (it != s.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
	
	return 0;
}

感谢花时间阅读这篇内容!

如果觉得有价值,欢迎点赞支持、收藏备用,或分享给同行。你的认可,是我持续输出高质量内容的最大动力。

我们下期再见喽!!!