C++ STL库set详解

1. set:

1. 核心作用

set(集合) 是 C++ STL 中的关联容器,用于存储唯一、有序的元素。主要作用包括:

  1. 去重存储:自动过滤重复元素

  2. 自动排序:元素按指定顺序自动排列

  3. 快速检索:基于平衡二叉搜索树(通常是红黑树)实现 O(log n) 的查找

  4. 范围查询:支持查找特定范围内的元素

  5. 集合运算:支持交集、并集、差集等数学集合操作

2. 关键特性

特性描述
唯一性容器内元素唯一,不允许重复
有序性元素自动排序(默认升序)
不可修改元素值不可修改(会破坏树结构)
动态大小大小可根据需要动态变化
双向迭代器set提供双向迭代器,可以向前和向后遍历,但不支持随机访问。

3. 底层实现

// set 底层通常实现为红黑树(平衡二叉搜索树)
template <typename Key, 
          typename Compare = std::less<Key>,
          typename Allocator = std::allocator<Key>>
class set {
    // 底层是红黑树,通常是 std::_Rb_tree
};

4. 接口函数:

  1. 构造函数:

    // 空set,使用默认的比较函数和分配器
    set<Key, Compare, Allocator> set1;
    // 空set,指定比较函数comp
    set<Key, Compare, Allocator> set2(comp); 
    // 拷贝构造
    set<Key, Compare, Allocator> set3(set1); 
    // 用迭代器范围构造
    set<Key, Compare, Allocator> set4(set1.begin(), set1.end()); 

    // 默认构造函数(升序)
    set<int> s1;
    // 初始化列表
    set<int> s2 = {3, 1, 4, 1, 5, 9};  // {1, 3, 4, 5, 9}
    // 复制构造函数
    set<int> s3(s2);
    // 迭代器范围初始化
    int arr[] = {2, 4, 6, 8};
    set<int> s4(arr, arr + 4);
    // 自定义排序规则(降序)
    set<int, greater<int>> s5 = {5, 2, 8, 1};  // {8, 5, 2, 1}
  2. 赋值操作:

    set1 = set2; // 赋值运算符
    set1 = {value1, value2, ...}; // 初始化列表赋值
  3. 迭代器:

    begin() / cbegin():返回指向第一个元素的迭代器
    end() / cend():返回指向末尾(最后一个元素之后)的迭代器
    rbegin() / crbegin():返回指向第一个元素的反向迭代器
    rend() / crend():返回指向末尾的反向迭代器
    c = const;表示此迭代器指向的内容不可修改

    set<int> s = {1, 3, 5, 7, 9};
    // 正向迭代器(升序遍历)
    for (auto it = s.begin(); it != s.end(); ++it) {
        cout << *it << " ";  // 1 3 5 7 9
    }
    // 反向迭代器(降序遍历)
    for (auto rit = s.rbegin(); rit != s.rend(); ++rit) {
        cout << *rit << " ";  // 9 7 5 3 1
    }
    // 范围for循环(C++11)
    for (const auto& val : s) {
        cout << val << " ";
    }
  4. 容量:

    empty():检查是否为空
    size():返回元素数量
    max_size():返回可容纳的最大元素数量

  5. 修改器:

    insert(const value_type& value):插入一个元素,返回一个pair,包含一个迭代器(指向插入的元素)和一个bool(表示是否插入成功)
    insert(value_type&& value):移动插入
    insert(iterator hint, const value_type& value):在hint位置附近插入,可能提高效率
    insert(InputIt first, InputIt last):插入一个范围

    emplace(args...):直接构造元素,避免拷贝
    emplace_hint(hint, args...):在hint位置附近直接构造

    erase(iterator pos):删除迭代器指向的元素
    erase(iterator first, iterator last):删除一个范围
    erase(const key_type& key):删除键为key的元素,返回删除的元素个数(0或1)

    swap(set2):交换两个set
    clear():清空所有元素

    set<int> s = {10, 20, 30, 40, 50};
    
    // 插入单个元素
    s.insert(10);
    s.insert(20);
    s.insert(10);   // 重复元素,插入失败
    // 插入带返回值
    auto result = s.insert(30);  // 返回 pair<iterator, bool>
    // result.first  -> 指向插入元素的迭代器
    // result.second -> 插入成功与否(true/false)
    // 插入多个元素
    s.insert({40, 50, 60});
    // 使用迭代器提示插入位置(效率提示)
    auto it = s.begin();
    s.insert(it, 25);  // it 只是提示,不一定插入在该位置
    // 通过值删除(返回删除的元素个数)
    size_t count = s.erase(20);  // count = 1
    // 通过迭代器删除
    auto it = s.find(30);
    if (it != s.end()) {
        s.erase(it);  // 删除30
    }
    // 删除一个范围
    auto first = s.find(40);
    auto last = s.end();
    s.erase(first, last);  // 删除40和50
    // 清空所有元素
    s.clear();
  6. 查找:

    count(const key_type& key):返回键等于key的元素个数(0或1,因为唯一性)
    find(const key_type& key):返回指向键等于key的元素的迭代器,如果没找到则返回end()
    equal_range(const key_type& key):返回一个pair,包含两个迭代器,表示键等于key的元素范围(对于set,这个范围最多包含一个元素)
    lower_bound(const key_type& key):返回指向第一个键>=key的元素的迭代器
    upper_bound(const key_type& key):返回指向第一个键>key的元素的迭代器

    set<int> s = {10, 20, 30, 40, 50};
    // find() - O(log n)
    auto it = s.find(30);  // 返回指向30的迭代器,找不到返回 s.end()
    // count() - O(log n)
    int exists = s.count(20);  // 返回1(存在)或0(不存在)
    // lower_bound() - 第一个不小于key的元素
    auto lb = s.lower_bound(25);  // 指向30
    // upper_bound() - 第一个大于key的元素
    auto ub = s.upper_bound(25);  // 指向30
    // equal_range() - 返回等于key的范围(对set意义不大,总是0或1个元素)
    auto range = s.equal_range(30);  // 返回 pair<iterator, iterator>
  7.  观察器:

    key_comp():返回比较键的函数对象
    value_comp():返回比较值的函数对象(对于set,key和value是一样的)
    //用这两个函数获取你的set元素排序比较的逻辑函数
  8. 非成员函数:

    operator==, !=, <, <=, >, >=:比较两个set
    swap(set1, set2):交换两个set

2. multiset:

multiset是关联容器,类似于set,但允许重复元素。它存储的元素按照特定的排序准则自动排序,默认是升序。multiset通常也基于红黑树实现。

主要特性:

  1. 允许重复元素。

  2. 自动排序(默认升序,可自定义比较函数)。

  3. 插入、删除和查找操作的时间复杂度为O(log n)。

  4. 迭代器是双向迭代器。

与set的主要区别:

  1. set中元素必须唯一,而multiset允许重复。

  2. multiset的insert操作总是成功的(因为可以重复),返回指向插入元素的迭代器。

  3. 在multiset中,count()函数可能返回大于1的值。

详细差异:

由于multiset允许重复元素,所以一些操作的行为会有所不同,例如:

  • insert: 总是插入,返回指向新插入元素的迭代器。

  • erase: 删除指定元素,返回删除的元素个数(可能大于1)。

  • count: 返回指定元素在容器中的个数。

  • find: 返回指向第一个等于给定值的元素的迭代器。

  • equal_range: 返回一个pair,表示等于给定值的元素范围。

对比维度setmultiset
元素唯一性必须唯一允许重复
插入返回值类型pair<iterator, bool>iterator
插入行为重复元素插入失败总是插入成功
count() 返回值0 或 10 到 n
erase(value) 返回值0 或 1删除的元素个数(0到n)
equal_range() 用途形式上的(返回单个元素或无)实际获取重复元素范围
内存占用每个值只存储一次每个值可能存储多次
底层节点数等于唯一元素个数等于总插入元素个数

1. 插入操作差异

// ==================== set ====================
set<int> s;
auto result1 = s.insert(10);   // 返回: pair<iterator, bool>
// result1.first -> 迭代器
// result1.second -> true (插入成功)

auto result2 = s.insert(10);   // 返回: pair<iterator, bool>
// result2.first -> 指向已存在10的迭代器
// result2.second -> false (插入失败)

// ==================== multiset ====================
multiset<int> ms;
auto it1 = ms.insert(10);      // 返回: iterator
// it1 -> 指向新插入的10

auto it2 = ms.insert(10);      // 返回: iterator  
// it2 -> 指向第二个10 (总是成功)

差异总结表:

特性set::insert()multiset::insert()
返回值类型pair<iterator, bool>iterator
返回值.first指向插入位置的迭代器指向新插入元素的迭代器
返回值.second插入成功与否(true/false)无此成员
插入重复元素失败,返回false成功,创建新节点
时间复杂度O(log n)O(log n)

2. 删除操作差异

// ==================== set ====================
set<int> s = {10, 20, 30};
size_t n1 = s.erase(20);      // n1 = 1
size_t n2 = s.erase(20);      // n2 = 0 (元素不存在)
size_t n3 = s.erase(30);      // n3 = 1

// ==================== multiset ====================  
multiset<int> ms = {10, 20, 20, 30, 30, 30};
size_t m1 = ms.erase(20);     // m1 = 2 (删除所有20)
size_t m2 = ms.erase(20);     // m2 = 0 (元素不存在)
size_t m3 = ms.erase(30);     // m3 = 3 (删除所有30)

差异总结表:

特性set::erase(value)multiset::erase(value)
返回值含义删除的元素个数删除的元素个数
返回值范围0 或 10 到 n
删除行为删除找到的元素(最多1个)删除所有匹配的元素
通过迭代器删除删除单个元素删除单个元素

3. 查找和计数差异

// ==================== set ====================
set<int> s = {10, 20, 30};
int c1 = s.count(20);    // c1 = 1
int c2 = s.count(40);    // c2 = 0
bool exists = c1 > 0;    // 判断存在性

// ==================== multiset ====================
multiset<int> ms = {10, 20, 20, 30, 30, 30};
int m1 = ms.count(20);   // m1 = 2
int m2 = ms.count(30);   // m2 = 3
int m3 = ms.count(40);   // m3 = 0

差异总结表:

特性set::count()multiset::count()
返回值含义元素是否存在元素出现次数
返回值范围0 或 10 到 n
时间复杂度O(log n)O(log n + k),其中k是重复次数
主要用途检查存在性统计频率

4. 范围查询差异

// ==================== set ====================
set<int> s = {10, 20, 30, 40, 50};
auto range1 = s.equal_range(30);
// range1.first -> 指向30
// range1.second -> 指向40
// distance(range1.first, range1.second) = 1 (总是0或1)

// ==================== multiset ====================
multiset<int> ms = {10, 20, 20, 30, 30, 30, 40};
auto range2 = ms.equal_range(30);
// range2.first -> 指向第一个30
// range2.second -> 指向40 (最后一个30的下一个位置)
// distance(range2.first, range2.second) = 3

差异总结表:

特性set::equal_range()multiset::equal_range()
实际意义形式上的,总是0或1个元素真正的"相等范围",可能有多个元素
使用频率较少使用(find足够)经常使用(处理重复元素)
与count关系count() ∈ {0, 1}count() = distance(first, second)
典型用途获取单个元素的迭代器遍历所有重复元素

5. 迭代器遍历差异

// ==================== set ====================
set<int> s = {10, 20, 20, 30};  // 实际存储 {10, 20, 30}
for (int val : s) {
    // 遍历:10, 20, 30 (每个值只出现一次)
}

// ==================== multiset ====================
multiset<int> ms = {10, 20, 20, 30};  // 存储 {10, 20, 20, 30}
for (int val : ms) {
    // 遍历:10, 20, 20, 30 (保留所有插入)
}

6. 唯一性保证

// set - 自动去重
set<int> s;
s.insert(10);
s.insert(10);
s.insert(10);
// s.size() = 1, 只包含一个10

// multiset - 保留所有
multiset<int> ms;
ms.insert(10);
ms.insert(10);
ms.insert(10);
// ms.size() = 3, 包含三个10

7. 查找稳定性

// set - find() 返回唯一元素的迭代器
set<int> s = {10, 20, 30};
auto it = s.find(20);  // 总是找到同一个20

// multiset - find() 返回第一个匹配元素的迭代器
multiset<int> ms = {10, 20, 20, 20, 30};
auto mit = ms.find(20);  // 找到第一个20,不是随机的

8. 删除稳定性

multiset<int> ms = {10, 20, 20, 20, 30};

// 删除特定位置的20
auto it = ms.find(20);
if (it != ms.end()) {
    ms.erase(it);  // 只删除第一个20
}

// 删除所有20
ms.erase(20);  // 删除剩余的两个20

性能差异详表

操作set 复杂度multiset 复杂度差异说明
insert(value)O(log n)O(log n)set需要检查重复,multiset直接插入
erase(value)O(log n + 1)O(log n + k)multiset需要删除所有k个重复元素
erase(iterator)O(1) 平均O(1) 平均相同
find(value)O(log n)O(log n)相同
count(value)O(log n)O(log n + k)multiset需要遍历重复元素
lower_bound()O(log n)O(log n)相同
upper_bound()O(log n)O(log n)相同
equal_range()O(log n)O(log n)相同
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值