set/multiset和map/multimap

一、set / multiset

1. 基本概念

  • 头文件#include <set>

  • 定义 :集合(set)和多重集合(multiset),用于存储键值(key)

  • 特点

    • set不允许重复元素,每个键唯一。插入重复元素会被忽略。

    • multiset允许重复元素

    • 默认会根据键值自动排序 (升序,使用 std::less<Key>)。

    • 底层结构通常是红黑树 ,查找、插入、删除的时间复杂度为 O(log n)

  • 元素不可修改:一旦插入,键值不可直接修改(否则破坏有序性),需先删除再插入新值。

2. 常用成员函数

2.1 构造与赋值
cpp 复制代码
set<int> s;                     // 空set
set<int> s2 = {3,1,4,1,5};      // 初始化列表,结果{1,3,4,5}
multiset<int> ms = {3,1,4,1,5}; // {1,1,3,4,5}
set<int> s3(s.begin(), s.end()); // 迭代器范围构造
2.2 插入元素
cpp 复制代码
// 插入单个值,返回 pair<iterator, bool>
auto ret = s.insert(10);  
// ret.first 是迭代器,指向插入位置(或已存在的元素)
// ret.second 为 bool:true表示插入成功,false表示元素已存在

// 提供位置提示(仅作优化,不强制插入位置)
auto it = s.insert(s.begin(), 25);  // 从 begin() 开始搜索,返回迭代器

// 插入区间
s.insert(other.begin(), other.end());

// multiset 的 insert 总是成功,返回指向新元素的迭代器(无 bool)
auto it_ms = ms.insert(5);
2.3 删除元素
cpp 复制代码
// 删除指定值:返回删除元素个数(set为0或1,multiset可>1)
size_t count = s.erase(10);

// 通过迭代器删除单个元素
s.erase(it);                 // 返回被删元素后一个位置的迭代器(C++11起)
s.erase(s.begin(), s.end()); // 删除区间
2.4 查找
cpp 复制代码
// find(): 返回迭代器,未找到返回 end()
auto it = s.find(20);
if (it != s.end()) { /* 找到了 */ }

// count(): 元素出现的次数,set中只能是0或1,multiset中可以是任意值
int cnt = s.count(5);

// lower_bound / upper_bound / equal_range
auto lb = s.lower_bound(10); // 第一个 >=10 的元素
auto ub = s.upper_bound(10); // 第一个 >10 的元素
auto range = s.equal_range(10); // 返回 pair<iterator,iterator> 包含所有等于10的元素
// range.first == lower_bound(10), range.second == upper_bound(10)
2.5 容量与判空
cpp 复制代码
s.empty();   // 是否为空
s.size();    // 元素数量
s.max_size(); // 可能容纳的最大元素数
2.6 迭代器
cpp 复制代码
set<int>::iterator it = s.begin();  // 指向最小元素
auto rit = s.rbegin();             // 反向迭代器,指向最大元素
// 所有迭代器均为双向迭代器(Bidirectional),支持++、--,不支持随机访问
// 迭代过程按排序顺序遍历
2.7 观察器(获取比较函数等)
cpp 复制代码
auto comp = s.key_comp();    // 返回键比较函数
auto val_comp = s.value_comp(); // 对于 set,等价于 key_comp

3. 自定义排序规则

set 的默认排序使用 std::less<Key>,可以传递自定义仿函数或函数指针:

cpp 复制代码
// 降序set
set<int, greater<int>> s_desc = {1,3,2}; // {3,2,1}

// 自定义结构体比较
struct Person {
    string name;
    int age;
};
struct Cmp {
    bool operator()(const Person& a, const Person& b) const {
        return a.age < b.age;  // 按年龄升序
    }
};
set<Person, Cmp> people;

注意 :比较函数必须满足严格弱序 (strict weak ordering),即不能出现 a < bb < a 同时为 true 的情况。

4. 使用场景与注意点

  • 自动去重 & 排序 :需要有序且不重复的数据时用 set

  • 允许重复的有序集合 :用 multiset,可用于实现优先队列等。

  • 不可修改键值:如需修改,需删除后插入。

  • 时间复杂度:插入/删除/查找均为 O(log n)。


二、map / multimap

1. 基本概念

  • 头文件#include <map>

  • 定义 :映射(字典),存储键值对(key-value)

    • map键唯一,不允许重复 key。

    • multimap允许重复键

  • 默认按键升序排序

  • 底层通常为红黑树,操作复杂度 O(log n)。

  • 元素类型std::pair<const Key, T>,键不可修改(const),值可修改。

2. 常用成员函数

2.1 构造与赋值
cpp 复制代码
map<string, int> m;                            // 空map
map<string, int> m2 = {{"apple",3}, {"banana",5}};
map<string, int> m3(m2.begin(), m2.end());
multimap<string, int> mm;
2.2 插入元素
cpp 复制代码
// 1. 使用 pair
m.insert(pair<string, int>("peach", 7));
m.insert(make_pair("peach", 7));
m.insert({"peach", 7});  // C++11 初始化列表

// 2. insert 返回 pair<iterator, bool>
auto ret = m.insert({"apple", 3});
if (!ret.second) {
    cout << "apple already exists, value = " << ret.first->second << endl;
}

// 3. 使用下标操作符(仅 map 支持,multimap 无此操作)
m["orange"] = 8;   // 若"orange"不存在,则插入{"orange", 8}
                   // 若已存在,则修改其 value 为 8
// 注意:使用 m[key] 会先调用默认构造函数创建 value,然后赋值(多了一次构造)
// 读取时若不存在会插入带默认值的元素,慎用!

// 4. emplace (C++11) 原地构造,更高效
m.emplace("grape", 12);
// multimap 的 insert 和 emplace 都只返回指向新元素的迭代器(无 bool)
2.3 访问元素
cpp 复制代码
// at(): 有边界检查,若 key 不存在抛出 std::out_of_range 异常
int val = m.at("apple");

// 下标操作符 []:若 key 不存在则插入默认值,返回 value 的引用
m["banana"] = 10;   // 修改现有值
int x = m["nonexistent"]; // 插入 {"nonexistent", 0}

// 遍历查找
auto it = m.find("apple");
if (it != m.end()) {
    cout << it->first << " -> " << it->second;
}
2.4 删除元素
cpp 复制代码
m.erase("apple");           // 按 key 删除,返回删除个数(map:0或1)
m.erase(it);                // 通过迭代器删除
m.erase(m.begin(), m.end());// 删除区间
2.5 其他查询
cpp 复制代码
size_t cnt = m.count("key"); // map 只能是0或1,multimap可>1
auto lb = m.lower_bound("b"); // 第一个 key>="b" 的迭代器
auto ub = m.upper_bound("b");
auto range = m.equal_range("b"); // pair of iterators
2.6 容量与迭代
cpp 复制代码
m.empty();
m.size();
m.max_size();
for (auto& p : m) {
    cout << p.first << " : " << p.second << endl;
}

3. 自定义排序

与 set 类似,需提供比较键的仿函数:

cpp 复制代码
struct DescCmp {
    bool operator()(const string& a, const string& b) const {
        return a > b; // 按 key 降序
    }
};
map<string, int, DescCmp> desc_map;
// 对于 multimap 同理

4. mapmultimap 的区别

特性 map multimap
键唯一性 唯一 可重复
operator[] 支持 不支持(因为键不唯一,无法确定引用哪个值)
insert 返回值 pair<iterator, bool> iterator
equal_range 使用 很少需要 常用,用于获取所有重复键的范围

5. 实用技巧与注意事项

  • 遍历删除 :删除当前迭代器后,返回值可作为下一个迭代器(C++11前使用 it++ 技巧)。

    cpp 复制代码
    for (auto it = m.begin(); it != m.end(); ) {
        if (condition) {
            it = m.erase(it);  // C++11起返回下一个迭代器
        } else {
            ++it;
        }
    }
  • 避免不必要的默认构造 :检查 key 是否存在时,不要使用 m[key],改用 find()count()

  • 键为常量map<Key,T>::value_typepair<const Key, T>,不能通过迭代器修改 key(因为 key 是 const)。如果需要修改 key,只能删除再插入。

  • 性能对比 :对于有序关联容器,红黑树保证了稳定的 O(log n),若需要纯粹的快速查找且无需排序,考虑 unordered_map/unordered_set(哈希表,平均 O(1))。

相关推荐
麦兜和小可的舅舅1 小时前
ClickHouse实时分布式集群设计方案选择探究
c++·分布式·clickhouse·kafka
code monkey.1 小时前
【Linux之旅】Linux UDP Socket 编程实战:从 Echo 服务器到多线程聊天室
linux·服务器·c++·udp
paeamecium2 小时前
【PAT甲级真题】- Dating(20)
c++·pat考试·pat
人道领域2 小时前
【LeetCode刷题日记】一篇搞懂->701.二叉搜索树的插入操作
java·数据结构·leetcode
Severus_black2 小时前
【初阶数据结构与算法】八大排序之归并排序,递归与非递归,一次性讲清!
数据结构·算法·排序算法
yunn_2 小时前
C++ 多线程笔记
开发语言·c++
I Promise342 小时前
C++ 类访问权限与三大特性详解
开发语言·c++
Huangjin007_2 小时前
【C++ STL篇(十二)】红黑树の影分身:一棵树如何同时化身 map 和 set(万字拆解+源码)
开发语言·c++
郝学胜-神的一滴11 小时前
Qt 高级开发 009: C++ Lambda 表达式
开发语言·c++·qt·软件构建