标准库容器——<set>

在 C++ 标准库中,set 是一种关联容器,主要用于存储不重复的元素,并能自动维持元素的有序性

set 的底层原理

set 的底层通常通过红黑树(Red-Black Tree) 实现(一种自平衡的二叉搜索树),这决定了它的核心特性:

 红黑树的特性(支撑 set 的行为)

红黑树是一种平衡二叉树,通过严格的规则(如节点颜色、左右子树高度差限制)保证树的 “平衡”,避免出现极端倾斜的情况(如退化为链表)。其核心优势是:

  • 有序性:二叉搜索树的特性决定了中序遍历(左→根→右)可以得到有序序列,因此 set 中的元素天然按升序(默认)排列。
  • 高效操作:插入、删除、查找元素的时间复杂度均为 O(log n)(n 为元素数量),因为红黑树的高度始终保持在 log n 级别。
set 的核心特性(由红黑树决定)
  • 元素唯一:红黑树不允许重复键值,因此 set 会自动去重(插入重复元素时会被忽略)。
  • 元素不可修改set 中的元素是 “常量”(const),不能通过迭代器直接修改。因为修改元素值会破坏红黑树的有序性(需要先删除旧元素,再插入新元素)。
  • 有序存储:元素始终按默认升序(或自定义排序规则)排列,遍历 set 时会按序访问元素。

set 的基本用法

使用 set 需包含头文件 <set>,并使用命名空间 std(或显式指定 std::set)。

定义与初始化

set 的模板定义为:template <class T, class Compare = less<T>, class Allocator = allocator<T>> class set;

  • T:元素类型(如 intstring 等)。
  • Compare:排序规则(默认 less<T>,即升序;可指定 greater<T> 为降序,或自定义比较器)。
#include <set>
#include <iostream>
using namespace std;

int main() {
    // 1. 空set(默认升序)
    set<int> s1;

    // 2. 用初始化列表初始化
    set<int> s2 = {3, 1, 4, 1, 5};  // 自动去重+排序,结果:{1,3,4,5}

    // 3. 用迭代器范围初始化(从其他容器复制元素)
    vector<int> vec = {2, 7, 1};
    set<int> s3(vec.begin(), vec.end());  // 结果:{1,2,7}

    // 4. 自定义排序(降序)
    set<int, greater<int>> s4 = {3, 1, 4};  // 结果:{4,3,1}

    return 0;
}
核心操作
        插入元素(insert
  • 插入单个元素:返回 pair<iterator, bool>,其中 bool 表示是否插入成功(重复元素会失败)。
  • 插入迭代器范围:批量插入元素(自动去重 + 排序)。
    set<int> s;
    
    // 插入单个元素
    auto res = s.insert(3);  // 插入成功,res.second = true
    res = s.insert(3);       // 重复插入,res.second = false(元素不变)
    
    // 批量插入
    s.insert({1, 4, 1, 5});  // 最终s:{1,3,4,5}
    查找元素(find

    返回指向目标元素的迭代器;若不存在,返回 s.end()。时间复杂度 O (log n)。

    set<int> s = {1,3,4,5};
    auto it = s.find(3);
    if (it != s.end()) {
        cout << "找到元素:" << *it << endl;  // 输出:3
    } else {
        cout << "元素不存在" << endl;
    }
    删除元素(erase

    支持通过迭代器范围删除元素,返回删除的元素个数(通过值删除时)。

    set<int> s = {1,3,4,5};
    
    // 1. 通过值删除
    int cnt = s.erase(3);  // 删除值为3的元素,cnt=1(若不存在则返回0)
    
    // 2. 通过迭代器删除(需先找到元素)
    auto it = s.find(4);
    if (it != s.end()) {
        s.erase(it);  // 删除迭代器指向的元素(4)
    }
    
    // 3. 删除所有元素(清空)
    s.clear();  // 等价于 s.erase(s.begin(), s.end())
    其他常用方法
    方法功能
    size()返回元素个数
    empty()判断是否为空(空返回 true
    begin()/end()返回首元素 / 尾后迭代器(用于遍历)
    rbegin()/rend()   返回反向迭代器(从尾到头遍历)
    count(val)返回值为 val 的元素个数(只能是 0 或 1),简洁判断val是否存在
遍历 set

由于 set 是有序的,遍历会按排序规则访问元素。支持迭代器遍历范围 for 循环

//  迭代器遍历
for (auto it = s.begin(); it != s.end(); ++it) {
    …………
}

for (auto t : s) {
       …………
}

不同的set

对比维度setmultisetunordered_set
底层实现红黑树(自平衡二叉搜索树)红黑树(同 set,仅处理重复逻辑不同)哈希表(链地址法解决冲突)
重复元素支持❌ 不允许(自动去重)✅ 允许(可存储多个相同值)❌ 不允许(自动去重)
排序特性✅ 自动按规则排序(默认升序)✅ 自动按规则排序(同 set,重复元素相邻)❌ 无序(按哈希值存储)
查找效率O (log n)(红黑树二分查找)O (log n)(同 set,需定位首个匹配元素)平均 O (1),最坏 O (n)(哈希冲突极端情况)
插入 / 删除效率O (log n)(红黑树旋转平衡)O (log n)(同 set,插入重复元素无需重新平衡)平均 O (1),最坏 O (n)
迭代器类型双向迭代器(支持 ++/--双向迭代器(同 set前向迭代器(仅支持 ++
count(val) 返回值0 或 1(仅判断存在性)≥0(返回 val 的实际重复次数)0 或 1(仅判断存在性)
erase() 行为按值删:删除 0 或 1 个元素;按迭代器删:删除 1 个元素按值删:删除所有值为 val 的元素;按迭代器删:删除 1 个元素同 set(无重复,删 0 或 1 个)
内存开销中等(红黑树节点存储元素 + 颜色 / 指针)中等(同 set,节点数更多)较高(哈希表需预留桶空间,可能有冗余)

一些细节

如果把一个数组加入unordered_set可以用insert或者初始化的时候使用迭代器

insert的方法:哈希表无法预知最终元素数量,只能在元素数量超过当前容量时被动扩容(每次扩容需要重新计算所有元素的哈希值并迁移,耗时较高)。但是insert 方法在插入重复元素时,会先检查元素是否已存在,若存在则直接返回(不执行插入),整个过程几乎没有额外开销(仅一次哈希查找)。

迭代器初始化:范围构造函数(unordered_set(InputIt first, InputIt last))可以通过迭代器范围提前计算出需要插入的元素总数(通过 distance(first, last)),从而预先分配足够大的内存空间(桶数量),避免插入过程中多次触发哈希表扩容。但是范围构造逻辑是遍历整个迭代器范围(包括所有重复元素),然后逐个插入非重复元素。即使元素重复,它也需要先遍历完所有元素(计算总长度用于预分配空间),再对每个元素执行 “检查是否存在 + 插入”。对于重复率极高的数组,这种 “先全量遍历再去重” 的操作,比第一种 “边遍历边去重” 更耗时(多了一次全量遍历的成本)。

posted @ 2025-09-29 15:55  mc12356  阅读(32)  评论(0)    收藏  举报  来源