std::set (C++)

1. 概述

定义

template<
    class Key,
    class Compare = std::less<Key>,
    class Allocator = std::allocator<Key>
>
class std::set;

特点

  • 有序:所有元素按严格弱序(由 Compare 决定)排列,不保证相同键的重复出现(键唯一)。

  • 唯一键:插入时如果集合中已有相同元素,不会增加新的节点。

  • 底层结构:通常基于红黑树(balanced binary search tree)实现。

2. 内部实现

  1. 红黑树

    • 平衡二叉查找树,保证从根到任一叶子路径的“黑色节点数”相同,从而令树高度为 O(log n)。
  2. 节点结构

    • 每个节点存储:
      • 元素值 Key
      • 左/右子指针和父指针
      • 一个颜色标记(红或黑)
  3. 拷贝与移动

    • 拷贝时会对整棵树进行深拷贝;移动构造则接管底层指针,无需逐节点复制。

3. 性能特征

操作平均复杂度最坏复杂度备注
insertO(log n)O(log n)包括查找插入位置与调整平衡
eraseO(log n)O(log n)按键删除时需旋转与重着色
findO(log n)O(log n)
clearO(n)O(n)释放所有节点
遍历O(n)O(n)中序遍历即可
  • 内存开销:

    • 每个节点需存储三个指针(左右、父)和一个颜色字段,相比哈希表更紧凑但比 std::vector 等顺序容器要高。
  • 迭代器失效规则:

    • 插入和删除会使仅被删除的节点的迭代器失效,其他节点迭代器保持有效。

4. 常用 API

// 构造与析构
std::set<Key> s1;                             // 默认构造
std::set<Key> s2({k1, k2, k3});               // 初始化列表
std::set<Key, Compare> s3(comp);              // 指定比较器

// 大小与容量
bool empty() const noexcept;
size_t size() const noexcept;
size_t max_size() const noexcept;

// 元素访问
iterator find(const Key& key);
size_t count(const Key& key) const;           // 要么 0,要么 1

// 插入
std::pair<iterator,bool> insert(const Key& key);
iterator insert(iterator hint, const Key& key);
template<class... Args>
std::pair<iterator,bool> emplace(Args&&... args);

// 删除
size_t erase(const Key& key);
iterator erase(iterator pos);
iterator erase(iterator first, iterator last);

// 遍历
iterator begin() noexcept;
iterator end() noexcept;
const_iterator cbegin() const noexcept;
const_iterator cend() const noexcept;

// 有序查询
iterator lower_bound(const Key& key);         // ≥ key 的第一个位置
iterator upper_bound(const Key& key);         // > key 的第一个位置
std::pair<iterator,iterator> equal_range(const Key& key);

// 比较器与分配器访问
Compare key_comp() const;
Compare value_comp() const;                   // 同 key_comp
Allocator get_allocator() const noexcept;

// 交换
void swap(std::set& other) noexcept;

5. 使用示例

#include <set>
#include <iostream>

int main() {
    std::set<int> s;

    // 插入
    s.insert(3);
    s.emplace(1);
    s.insert({5, 2, 4});

    // 查找
    if (s.find(2) != s.end()) {
        std::cout << "2 存在\n";
    }

    // 遍历(中序:1,2,3,4,5)
    for (int x : s) {
        std::cout << x << " ";
    }
    std::cout << "\n";

    // 有序查询
    auto it = s.lower_bound(3);  // 指向 3
    std::cout << "lower_bound(3): " << *it << "\n";

    // 删除
    s.erase(3);                  // 删除值为 3 的节点

    // 范围删除
    s.erase(s.begin(), s.lower_bound(4)); // 删除所有 <4 的元素

    // 清空
    s.clear();
}

6. 自定义比较器

当需要自定义排序规则(如降序或复合键)时,可提供自定义比较器:

struct Desc {
    bool operator()(int a, int b) const {
        return a > b;  // 降序
    }
};

std::set<int, Desc> s_desc;  // 插入后,将按从大到小顺序存储

对于复合类型:

struct Person {
    std::string name;
    int age;
};

struct ByAgeName {
    bool operator()(Person const& a, Person const& b) const {
        if (a.age != b.age) return a.age < b.age;
        return a.name < b.name;
    }
};

// 年龄升序,若年龄相同则按名字升序
std::set<Person, ByAgeName> roster;

7. 注意事项与优化

  1. 避免不必要的拷贝

    • insert(const Key&) 会拷贝一次;若可移动,优先使用 insert(Key&&) 或 emplace()。
  2. hint 参数

    • insert(hint, key):若能提供一个接近正确位置的迭代器 hint,可将插入复杂度降到常数时间。
  3. 批量插入

    • 对初始化列表或范围插入,建议先 reserve()(C++23 起支持)或构造时传入范围,以减少重平衡次数。
  4. 避免迭代器失效

    • 删除或插入仅影响相关节点迭代器,其他迭代器保持有效。

8. 使用建议

  • 适用场景

    • 需要自动排序且元素唯一时,首选 std::set。
    • 需要查询前驱/后继、区间操作(lower_bound、upper_bound、中序遍历)时非常方便。
  • 不适用场景

    • 需要允许重复键或多对一映射时,改用 std::multiset 或 std::map。
    • 对性能有极致要求且不在意顺序时,可考虑 std::unordered_set(哈希实现,平均 O(1))。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值