Set的用法(c++)

1. 关联容器概述

在 C++ 的标准模板库(STL)里,容器可分为以下几类:

 

  • 顺序容器:像 vectorlistdeque 等,元素的存储顺序由插入顺序决定。
  • 关联容器:例如 setmap,这类容器会根据键(key)来自动对元素进行排序和存储。
  • 无序容器:包含 unordered_setunordered_map,基于哈希表实现,元素是无序的。

 

std::set 属于关联容器,它的主要特点是存储唯一的元素,并且这些元素会按照键的顺序排列。

 

2. std::set 的核心特性

  • 元素唯一性set 中不会存在重复的元素。当你尝试插入一个已经存在的元素时,插入操作会被忽略。
  • 自动排序:元素会根据键的比较规则自动排序,默认是按照升序排列。
  • 不可修改键值:一旦元素被插入到 set 中,就不能直接修改其键值,因为这可能会破坏排序结构。若要修改元素,需先删除该元素,再插入新元素。
  • 高效操作:插入、删除和查找操作的时间复杂度都是 O (log n),这是因为 set 通常是基于红黑树(一种自平衡二叉搜索树)实现的。

 

3. 基本用法

3.1 头文件与命名空间

使用 std::set 时,需要包含 <set> 头文件,代码如下:

 

#include <set>
using namespace std; // 或者使用 std::set

 

3.2 集合的定义与初始化

下面是几种常见的定义和初始化 set 的方式:

 

// 定义一个空的 set,存储 int 类型的元素
set<int> s1;

// 用初始化列表初始化 set
set<int> s2 = {3, 1, 4, 1, 5}; // 实际存储 {1, 3, 4, 5}

// 拷贝构造函数
set<int> s3(s2);

// 范围初始化(从另一个容器)
vector<int> v = {5, 2, 6};
set<int> s4(v.begin(), v.end()); // {2, 5, 6}

 

3.3 插入元素

可以使用 insert() 方法向 set 中插入元素,示例如下:

 

set<int> s;
s.insert(10);    // 插入单个元素
s.insert(20);
s.insert(10);    // 重复元素,插入失败

int arr[] = {30, 40, 30};
s.insert(arr, arr + 3); // 从数组插入范围,实际插入 {30, 40}

// 检查插入是否成功(返回值是一个 pair)
auto result = s.insert(50);
if (result.second) {
    cout << "插入成功,元素: " << *(result.first) << endl;
} else {
    cout << "插入失败,元素已存在" << endl;
}

 

3.4 查找元素

查找元素主要有以下几种方法:

 

set<int> s = {1, 3, 5, 7};

// 使用 find() 方法查找元素
auto it = s.find(5);
if (it != s.end()) {
    cout << "找到元素: " << *it << endl;
} else {
    cout << "元素不存在" << endl;
}

// 使用 count() 方法判断元素是否存在
if (s.count(7) > 0) {
    cout << "元素 7 存在" << endl;
}

// 查找第一个不小于给定值的元素(lower_bound)
auto lb = s.lower_bound(4);
cout << "lower_bound(4): " << *lb << endl; // 输出 5

// 查找第一个大于给定值的元素(upper_bound)
auto ub = s.upper_bound(4);
cout << "upper_bound(4): " << *ub << endl; // 输出 5

// 查找范围 [lower, upper)
auto range = s.equal_range(3);
cout << "equal_range(3): [" << *(range.first) 
     << ", " << *(range.second) << ")" << endl;

 

3.5 删除元素

删除元素的操作如下:

set<int> s = {10, 20, 30, 40, 50};

s.erase(30);        // 删除值为 30 的元素
s.erase(s.find(40)); // 删除迭代器指向的元素

// 删除范围 [first, last)
auto first = s.find(10);
auto last = s.find(50);
s.erase(first, last); // 删除 10 和 20

s.clear(); // 清空集合

 

3.6 遍历集合

可以通过以下几种方式遍历 set 中的元素:

 

set<int> s = {2, 4, 1, 3};

// 使用迭代器(升序)
cout << "升序遍历: ";
for (auto it = s.begin(); it != s.end(); ++it) {
    cout << *it << " ";
}
cout << endl;

// 使用范围-based for 循环(C++11 及以后)
cout << "升序遍历(简化版): ";
for (int x : s) {
    cout << x << " ";
}
cout << endl;

// 使用反向迭代器(降序)
cout << "降序遍历: ";
for (auto it = s.rbegin(); it != s.rend(); ++it) {
    cout << *it << " ";
}
cout << endl;

 

4. 自定义比较函数

默认情况下,set 使用 std::less 进行元素比较,实现升序排列。你也可以自定义比较规则:

 

4.1 使用函数对象(Functor)
// 自定义比较函数:降序排列
struct Greater {
    bool operator()(const int& a, const int& b) const {
        return a > b;
    }
};

set<int, Greater> s; // 降序排列的集合
s.insert(1);
s.insert(3);
s.insert(2);
// 集合中的元素顺序:3, 2, 1

 

4.2 使用 Lambda 表达式(C++11 及以后)
// 使用 lambda 表达式定义降序比较
auto cmp = [](int a, int b) { return a > b; };
set<int, decltype(cmp)> s(cmp);

s.insert(10);
s.insert(20);
s.insert(15);
// 集合中的元素顺序:20, 15, 10

 

5. 存储自定义类型

set 存储自定义类型的对象时,需要定义比较函数,以便集合进行排序:

 

class Person {
public:
    string name;
    int age;
    
    Person(string n, int a) : name(n), age(a) {}
    
    // 重载 < 运算符,用于集合排序
    bool operator<(const Person& other) const {
        return age < other.age; // 按年龄排序
    }
};

// 使用自定义类型的集合
set<Person> people;
people.insert(Person("Alice", 25));
people.insert(Person("Bob", 20));
people.insert(Person("Charlie", 30));

// 遍历集合(按年龄升序)
for (const auto& p : people) {
    cout << p.name << " (" << p.age << ")" << endl;
}

 

6. 常用方法总结

下面是 std::set 的一些常用方法:

方法描述
insert(value)插入元素,如果元素已存在则不插入,返回一个包含迭代器和布尔值的 pair。
erase(value)删除指定值的元素,返回删除的元素个数(0 或 1)。
erase(iterator)删除迭代器指向的元素。
find(value)查找元素,返回指向该元素的迭代器,如果未找到则返回 end()
count(value)检查元素是否存在,返回 0 或 1。
lower_bound(key)返回第一个不小于 key 的元素的迭代器。
upper_bound(key)返回第一个大于 key 的元素的迭代器。
equal_range(key)返回一个 pair,包含 lower_boundupper_bound
size()返回集合中元素的数量。
empty()判断集合是否为空。
clear()清空集合中的所有元素。
begin()返回指向集合第一个元素的迭代器(升序)。
end()返回指向集合末尾的迭代器(升序)。
rbegin()返回指向集合第一个元素的反向迭代器(降序)。
rend()返回指向集合末尾的反向迭代器(降序)。

 

7. 与其他容器的对比

容器元素唯一性元素顺序查找效率插入 / 删除效率支持随机访问
std::set唯一自动排序O(log n)O(log n)
std::unordered_set唯一无序O (1) 平均O (1) 平均
std::vector不强制插入顺序O(n)O (n) 尾部 O (1)
std::list不强制插入顺序O(n)O(1)

 

8. 性能考虑

  • 插入 / 删除 / 查找:时间复杂度为 O (log n),因为 set 是基于红黑树实现的。
  • 空间开销:由于使用树结构,每个节点需要额外存储父节点和子节点的指针,空间开销比顺序容器大。
  • 适用场景:适用于需要快速查找、保持元素有序且唯一的场景,例如去重、排序查询等。

9. 示例代码

下面是一个综合示例,展示了 std::set 的各种用法:

 

#include <iostream>
#include <set>
#include <string>

using namespace std;

int main() {
    // 创建一个存储字符串的集合,按字符串长度排序
    struct LengthCmp {
        bool operator()(const string& a, const string& b) const {
            return a.length() < b.length();
        }
    };
    
    set<string, LengthCmp> words;
    
    // 插入元素
    words.insert("apple");
    words.insert("banana");
    words.insert("cherry");
    words.insert("date"); // 长度为 4,会排在 "apple" 之前
    
    // 遍历集合(按字符串长度升序)
    cout << "按长度排序的单词:" << endl;
    for (const auto& word : words) {
        cout << word << " (长度: " << word.length() << ")" << endl;
    }
    
    // 查找长度为 5 的单词
    auto it = words.lower_bound("*****"); // 5 个字符的字符串
    if (it != words.end() && it->length() == 5) {
        cout << "找到长度为 5 的单词: " << *it << endl;
    } else {
        cout << "未找到长度为 5 的单词" << endl;
    }
    
    // 删除长度大于 5 的单词
    for (auto it = words.begin(); it != words.end(); ) {
        if (it->length() > 5) {
            it = words.erase(it); // erase 返回下一个迭代器
        } else {
            ++it;
        }
    }
    
    // 输出剩余元素
    cout << "删除长度大于 5 的单词后:" << endl;
    for (const auto& word : words) {
        cout << word << endl;
    }
    
    return 0;
}

 

10. 输出结果

按长度排序的单词:
date (长度: 4)
apple (长度: 5)
banana (长度: 6)
cherry (长度: 6)
找到长度为 5 的单词: apple
删除长度大于 5 的单词后:
date
apple

 

总结

std::set 是 C++ 中一个非常实用的关联容器,它能够高效地存储唯一元素并自动排序。通过自定义比较函数,你可以灵活控制元素的排序规则。在需要快速查找、去重和有序遍历的场景中,std::set 是一个理想的选择。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值