C++ STL set 完全指南:从基础用法到实战技巧

🔥个人主页:Cx330🌸

❄️个人专栏:《C语言》《LeetCode刷题集》《数据结构-初阶》《C++知识分享》

《优选算法指南-必刷经典100题》《Linux操作系统》:从入门到入魔

🌟心向往之行必能至


🎥Cx330🌸的简介:


目录

前言:

[一、set 核心特性与适用场景](#一、set 核心特性与适用场景)

[1. 核心特性](#1. 核心特性)

[2. 适用场景](#2. 适用场景)

[二、set 基础用法(含代码示例)](#二、set 基础用法(含代码示例))

[2.1 头文件与命名空间](#2.1 头文件与命名空间)

[2.2 初始化与插入:](#2.2 初始化与插入:)

​编辑

[2.3 查找与删除:](#2.3 查找与删除:)

[2.4 lower_bound 与 upper_bound:"定位神器"](#2.4 lower_bound 与 upper_bound:“定位神器”)

[三、multiset:支持重复 key](#三、multiset:支持重复 key)

[3.1 set与multiset差异:](#3.1 set与multiset差异:)

四、set实战技巧

[4.1:环形链表 II](#4.1:环形链表 II)

[4.2 两个数组的交集](#4.2 两个数组的交集)

结尾:


前言:

在 C++ STL(标准模板库)中,set 是一个极具实用价值的关联容器,核心特性是自动排序元素唯一。它底层基于红黑树(平衡二叉搜索树)实现,保证了插入、查找、删除等操作的高效性(时间复杂度均为 O (logn))。无论是数据去重、有序遍历,还是区间查询,set 都能轻松应对。本文将从基础概念到进阶实战,带你全面掌握 set 的用法


一、set 核心特性与适用场景

参考文档:set - C++ Reference

1. 核心特性

  • 有序性 :元素会按照默认规则(升序)自动排序,也支持自定义排序规则;
  • 唯一性不允许重复元素,插入重复值会被自动忽略;
  • 底层实现红黑树(平衡二叉搜索树),兼顾查询和插入效率;
  • 元素不可直接修改:set 的元素是 const 类型(修改会破坏排序结构),若需修改需先删除旧元素再插入新元素。

2. 适用场景

  • 数据去重并保持有序(如考试成绩排名、日志时间排序);
  • 高效查找元素是否存在(如用户 ID 校验);
  • 区间查询(如查找 [10, 50] 之间的数值);
  • 有序遍历需求(无需手动排序)。

set的声明


二、set 基础用法(含代码示例)

2.1 头文件与命名空间

使用set 需包含头文件 <set>,并使用 std 命名空间:

cpp 复制代码
#include <set>
#include <iostream>
using namespace std; // 简化代码,实际项目可根据需求选择是否使用

set相关接口:

2.2 初始化与插入:

set 支持多种插入方式,插入后自动去重并按升序排列 代码示例**(注意看注释)**:

  • 插入单个元素:insert(val),返回 pair<iterator, bool>(迭代器指向插入位置,bool 表示是否插入成功);
  • 插入多个元素:insert(initializer_list)(C++11+);
  • 插入范围元素:insert(begin, end)
cpp 复制代码
#include<iostream>
#include<set>
using namespace std;

void test_set1()
{
	set<int> s;
	s.insert(3);
	s.insert(1);
	s.insert(2);
	s.insert(5);
	s.insert(3);
	s.insert(5);
	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;

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

说明:元素会按照默认规则(升序)自动排序,也支持自定义排序规则;

2.3 查找与删除:

  • 根据键删除:erase(val),返回删除的元素个数(0 或 1,因为 set 无重复);
  • 根据迭代器删除:erase(iter),无返回值(需确保迭代器有效);
  • 范围删除:erase(begin, end),无返回值。
cpp 复制代码
void test_set1()
{
	set<int> s;
	s.insert(3);
	s.insert(1);
	s.insert(2);
	s.insert(5);
	s.insert(3);
	s.insert(5);
	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;

	int x = 0;
	cin >> x;
	s.erase(x);//在set里就删,不在就不删

	cout << s.erase(x) << endl;

	auto pos = s.find(x);
	if (pos != s.end())
	{
		s.erase(pos);
	}

	//单独判断在不在
	//if (s.count(x))
	//{
	//}

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

2.4 lower_bound 与 upper_bound:"定位神器"

set 的区间操作依赖lower_boundupper_bound,用于快速定位边界,结合erase可高效删除区间元素:

思考:如果我们要删除【3,8】区间的元素呢?

答:find()函数查找3和8,依次删除

那如果没有3这个元素呢?我们接着往下看:

cpp 复制代码
void test_set2()
{
	set<int> s;
	s.insert(3);
	s.insert(1);
	s.insert(2);
	s.insert(5);
	s.insert(3);
	s.insert(5);
	s.insert(6);
	s.insert(7);
	s.insert(9);

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

	//[3,8]之间的值都删掉
	//auto it = s.find(3);//如果没有3呢?
	
	//查找 >=3 的值(有3就返回3,没有3就返回比3大一点的值)
	auto it1 = s.lower_bound(3);
	//查找 <=8 的值(有8就返回8,没有8就返回比8小一点的值)
	auto it2 = s.upper_bound(8);
	s.erase(it1, it2);
	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl; 

}

说明:

  • lower_boundupper_bound 的查找时间复杂度:O(logN),是一种较为优秀的

三、multiset:支持重复 key

multiset 与 set 接口一致,核心差异是允许重复 key,适用于需要存储相同元素并统计频率的场景:

cpp 复制代码
void test_set3()
{
	multiset<int> s;
	s.insert(3);
	s.insert(1);
	s.insert(2);
	s.insert(5);
	s.insert(3);
	s.insert(5);
	s.insert(6);
	s.insert(3);

	//遍历结果:有序(不去重)  
	multiset<int>::iterator it = s.begin();
	while (it != s.end())
	{
		//*it = 1;//不能修改
		cout << *it << " ";
		++it;
	}
	cout << endl;

	//  multiset里查中序第一个3
	auto pos = s.find(3);
	//while (pos != s.end())
	while (pos != s.end() && *pos == 3)//只打印3
	{
		cout << *pos << " ";
		++pos;
	}
	cout << endl;

	//s.erase(3);

	// [ )
	typedef multiset<int>::iterator IT;
	//std::pair<multiset<int>::iterator, multiset<int>::iterator> ret = s.equal_range(3);
	//std::pair<IT, IT> ret = s.equal_range(3);
	auto ret = s.equal_range(3);


	cout << s.count(3) << endl;
	cout << s.erase(3) << endl;

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

int main()
{
	//test_set1();
	//test_set2();
	test_set3();


	return 0;
}

3.1 set与multiset差异:


四、set实战技巧

4.1:环形链表 II

题目链接142. 环形链表 II - 力扣(LeetCode)

说明:之前我们是利用快慢指针来解题,我们再来回顾一下快慢指针的做法:

图解:

C算法代码(快慢指针):

复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
 typedef struct ListNode ListNode;
struct ListNode *detectCycle(struct ListNode *head) 
{
    ListNode*slow=head;
    ListNode*fast=head;
    while(fast&&fast->next)
    {
        slow=slow->next;
        fast=fast->next->next;
        if(slow==fast)
        {
            //找相遇点
            ListNode*pcur=head;
            while(pcur!=slow)
            {
                pcur=pcur->next;
                slow=slow->next;
            }
            return pcur;
        }

    }
    return NULL;
}

C++算法代码:

cpp 复制代码
/**
 * 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)
        {
            auto it=s.find(cur);
            if(it==s.end())
            {
                s.insert(cur);
            }
            else
            {
                return *it;
            }
            cur=cur->next;
        }
        return nullptr;    
    }
};

4.2 两个数组的交集

题目链接349. 两个数组的交集 - 力扣(LeetCode)

图解:

C++算法代码:

cpp 复制代码
class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) 
    {
        //去重+排序
        set<int> s1(nums1.begin(),nums1.end());    
        set<int> s2(nums2.begin(),nums2.end());    
        vector<int> v;

        auto it1=s1.begin();
        auto it2=s2.begin();

        while(it1!=s1.end()&&it2!=s2.end())
        {
            if(*it1<*it2)
            {
                it1++;
            }
            else if(*it1>*it2)
            {
                it2++;
            }
            else
            {
                v.push_back(*it1);
                ++it1;
                ++it2;
            }
        }
        return v;
    }
};

结尾:

结语:掌握 set 的用法,能大幅简化 "去重 + 排序" 类场景的开发,提升代码效率和可读性。建议结合实际场景多练习,比如日志去重排序、数据区间统计等,加深对 set 特性的理解

相关推荐
white-persist1 小时前
【攻防世界】reverse | Reversing-x64Elf-100 详细题解 WP
c语言·开发语言·网络·python·学习·安全·php
FeiHuo565151 小时前
微信个人号开发中如何高效实现API二次开发
java·开发语言·python·微信
zmzb01031 小时前
C++课后习题训练记录Day33
开发语言·c++
csbysj20201 小时前
Bootstrap 折叠
开发语言
Want5951 小时前
C/C++贪吃蛇小游戏
c语言·开发语言·c++
阿昭L2 小时前
堆结构与堆排序
数据结构·算法
2***57422 小时前
人工智能在智能投顾中的算法
人工智能·算法
豆浆whisky2 小时前
Go并发模式选择指南:找到最适合你项目的并发方案|Go语言进阶(19)
开发语言·后端·golang
草莓熊Lotso2 小时前
《算法闯关指南:动态规划算法--斐波拉契数列模型》--01.第N个泰波拉契数,02.三步问题
开发语言·c++·经验分享·笔记·其他·算法·动态规划