首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >深度解析C++中的set的使用

深度解析C++中的set的使用

原创
作者头像
Undoom
发布2025-01-03 18:52:34
发布2025-01-03 18:52:34
8420
举报

1.序列式容器和关联式容器

前面我们已经接触过STL中的部分容器如:string、vector、list、deque、array、forward_list等,这些容器统称为序列式容器,因为逻辑结构为线性序列的数据结构,两个位置存储的值之间一般没有紧密的关联关系,比如交换一下,他依旧是序列式容器。顺序容器中的元素是按他们在容器中的存储位置来顺序保存和访问的。

关联式容器也是用来存储数据的,与序列式容器不同的是,关联式容器逻辑结构通常是非线性结构,两个位置有紧密的关联关系,交换一下,他的存储结构就被破坏了。顺序容器中的元素是按关键字来保存和访问的。关联式容器有map/set系列和unordered_map/unordered_set系列。本章节讲解的map和set底层是红黑树,红黑树是一颗平衡二叉搜索树。set是kev搜索场景的结构,map是key/value搜索场景的结构。

2.set的介绍

访问地址

  • set默认要求T支持小于比较,如果不支持或者想按自己的需求走可以自行实现仿函数传给第二个模版参数
  • set底层存储数据的内存是从空间配置器申请的,如果需要可以自己实现内存池,传给第三个参数。
  • 一般情况下,我们都不需要传后两个模版参数。
  • set底层是用红黑树实现,增删查效率是O(logN),迭代器遍历是走的搜索树的中序,所以是有序的。

前面部分我们已经学习了vector/list等容器的使用,STL容器接口设计,高度相似,所以这里我们就不再一个接口一个接口的介绍,而是直接带着大家看文档,挑比较重要的接口进行介绍。

3.set的构造和迭代器部分

set可以进行去重操作的,在去重的同时可以对插入进来的数字进行排序的操作

set的底层是搜索树,所以我们是不能进行修改的

代码语言:C++
复制
// empty (1) 无参默认构造  
explicit set (const key_compare& comp = key_compare(),
              const allocator_type& alloc = allocator_type());
// range (2) 迭代器区间构造
template <class InputIterator>
  set (InputIterator first, InputIterator last,
       const key_compare& comp = key_compare(),
       const allocator_type& = allocator_type());
       
// copy (3) 拷贝构造
set (const set& x);
// initializer list (5) initializer  列表构造
set (initializer_list<value_type> il,
     const key_compare& comp = key_compare(),
     const allocator_type& alloc = allocator_type());
     
// 迭代器是一个双向迭代器
iterator   -> a bidirectional iterator to const value_type
// 正向迭代器
iterator begin();
iterator end();
// 反向迭代器   
reverse_iterator rbegin();
reverse_iterator rend();

4.set的增删查

set的底层是搜索树,所以是不能进行修改操作的

insert

代码语言:C++
复制
int main()
{
	//这个是降序的,给一个大于的仿函数
	//set<int,greater<int>>s;
	
	//升序排列+去重
	//set<int>s;
	set<int>s = {10,6,8,9};


	s.insert(5);
	s.insert(2);
	s.insert(7);
	s.insert(5);
	s.insert({ 1,2,3 });
	//set<int>::iterator it = s.begin();
	auto it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	set<string> strset = { "sort","insert","add" };//string是按照ASCII进行比较大小的
	for (auto& e : strset)
	{
		cout << e << " ";
	}
	cout << endl;
	return 0;
}

erase

删除成功就返回1,没有删除成功就返回0

代码语言:C++
复制
int main()
{
	set<int>s = { 4,2,7,2,8,5,9 };
	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl;

	//s.erase(s.end()--);//因为迭代器是左闭右开的,end要进行减减操作的


	//删除容器中的最小值
	s.erase(s.begin());
	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl;

	int x;
	cin >> x;
	auto pos = s.find(x);
	if (pos != s.end())//end是一个非法的位置,是最后一个数据的下一个位置
	{
		s.erase(pos);
	}
	else
	{
		cout << x << "不存在" << endl;
	}
	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl;


	//算法库的find,时间复杂度O(N)
	auto pos1 = find(s.begin(), s.end(), x);

	//set自身实现的查找O(logN)
	auto pos2 = s.find(x);

	return 0;
}

find

两种find,但是我们set里面的find的效率更加高效,初次之外,string这个容器也有find,为的是寻找子串以及其他类型的字符串

代码语言:C++
复制
	//算法库的find,时间复杂度O(N)
	auto pos1 = find(s.begin(), s.end(), x);

	//set自身实现的查找O(logN)
	auto pos2 = s.find(x);

upper_bound和 lower_bound

代码语言:C++
复制
int main()
{
	set<int>myset;
	for (int i = 1; i < 10; i++)
	{
		myset.insert(i * 10);//10 20 30 40 50 60 70 80 90
	}
	for (auto e : myset)
	{
		cout << e << " ";
	}
	cout << endl;

	//实现查找到的[itlow,itup)包含[30,60)这段区间并且进行删除操作
	auto itlow = myset.lower_bound(30);//返回大于30的第一个值

	//返回>60的值
	auto itup = myset.upper_bound(60);

	//我们可以通过这种方法找到这个区间中包含我们想找的区间
	//通过这个方法可以给我们一个比我们想要找的区间大一些的空间,刚好包含我们想找的区间
	//然后我们就可以进行删除操作了

	myset.erase(itlow, itup);//通过这两个迭代区间进行删除操作
	for (auto e : myset)
	{
		cout << e << " ";
	}
	cout << endl;

	return 0;
}

通过两个直接将连个数据传进去,然后编译器就会找到刚好比这个区间大的一个区间

将我们要删除的区间进行删除的操作

5.multiset和set的差异

与set不同的是,multiset仅仅是进行排序,但是不进行去重操作的

如果一个数字出现多次的话,multiset中的find是只会返回第一个的,但是我们能根据这个加上迭代器将所有的这个数字打印出来

我们还能利用count算出这个数字出现的个数

miltiset中的erase可以直接将所以的这个数字删除了,一个不剩

代码语言:C++
复制
#include<iostream>
using namespace std;                                 
#include<set>
int main()
{
	//与set不同的是,multiset是排序,但是不进行去重操作的
	multiset<int>s = { 4,5,6,11,4,5,1,1, };
	auto it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	int x;
	cin >> x;
	auto pos = s.find(x);
	while (pos != s.end() && *pos == x)
	{
		cout << *pos << " ";
		++pos;
	}
	//find进行数据的寻找,如果有多个会返回第一个
	//因为我们的multiset会将数组进行排序的操作,默认从小到大的排序
	//那么我们这里利用find找到了第一个4,然后如果有相同的4的话那么都是跟在第一个4的后面的
	cout << endl;
	//相比set不同的是,count回返回x的实际个数
	cout << s.count(x) << endl;
	//conut如果这个数字有多个的话那么就会返回这个数字的个数的
	
	//相比set不同的是,erase给值时会删除所有的x
	s.erase(x);
	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl;

	return 0;
}

6.相关题目

349.两个数组的交集

题目地址

代码语言:C++
复制
class Solution
{
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2)
    {
        //依次进行比较
        //1.小的++
        //2.相等的就是交集,同时++,其中一个结束就是结束了

        //排序+去重
        set<int> s1(nums1.begin(),nums1.end());//set是可以利用迭代器区间进行初始化操作的
        set<int> s2(nums2.begin(),nums2.end());

        vector<int> ret;
        auto it1=s1.begin();
        auto it2=s2.begin();
        while(it1!=s1.end()&&it2!=s2.end())
        {
            //谁小谁就++
            if(*it1<*it2)
            {
                it1++;
            }
            else if(*it2<*it1)
            {
                it2++;
            }
            //相等的就是交集,然后我们同时++
            else
            {
                ret.push_back(*it1);
                it1++;
                it2++;
            }
        }
        return ret;


    }
};

我们先利用set进行排序操作

然后创建一个vector的容器来进行结果的存储

我们这个题的原理就是我们连个数组依次进行比较的操作

小的就进行++,大的不动

如果出现了两个数字相等的情况的话,那么我们就同时进行++操作

因为要求里面返回结果的元素都必须是唯一的

所以我们需要利用set进行去重的操作的

142.环形链表 II

题目链接

我们之前的思路是:

先利用快慢指针,直到我们的快慢指针相遇我们就停下来

然后在头节点创建一个指针,让这个指针和我们的慢指针一起运动,,直到我们的头结点指针遇到了慢指针我们就停下

那么当这两个指针相遇的时候我们就将当前的位置直接返回就行了,当前的位置就是我们所需要的入环节点处

那么我们这里的思路是将每个节点都放到我们的set里面,使用insert进行插入的操作

如果某个节点插入失败的话,那么就说明这个节点在set中已经是存在了的,那么我们直接阿静这个节点返回,这个节点就是我们所需要的环形链表的入环的第一个节点

代码语言:C++
复制
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution 
{
public:
    ListNode *detectCycle(ListNode *head)
    {
        set<ListNode*>s;
        ListNode*cur=head;

        while(cur)
        {
            if(s.count(cur))//如果这个节点在的话,就是说这个节点在set中已经存在了,那么这个count就会返回一个1
            {
                return cur;//我们直接把这个节点返回了,这个就是把环形链表的入环的第一个节点
            }
            else//如果不存在的话那么我们就进行插入的操作
            {
                s.insert(cur);
            }
            cur=cur->next;
        }
        return nullptr;//如果出了循环的话还没有结果的话那么就是不带环的,那么我们直接返回空就行了



    }
};

我们这里的思路就是使用cur进行遍历链表

然后再使用set中的count进行节点是否存在进行判断,如果存在的话就返回1,那么我们就字节返回这个节点,因为set中已经存在了一个相同的节点,如果再出现一次的话,那么我们直接就将这个节点返回了,因为这个节点是我们的环形链表的环节点那里的第一个节点

然后如果没有出现的话,我们就会进入到下面的else的语句中,我们直接进行插入,将节点直接插入到set实例化的对象s中去,

如果出了循环还没有结果返回的话,那么就说明这个链表不是带环的链表,那么我们直接返回空就行了

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.序列式容器和关联式容器
  • 2.set的介绍
  • 3.set的构造和迭代器部分
  • 4.set的增删查
    • insert
    • erase
    • find
    • upper_bound和 lower_bound
  • 5.multiset和set的差异
  • 6.相关题目
    • 349.两个数组的交集
    • 142.环形链表 II
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档