当前位置: 首页 > news >正文

std::set、std::multiset 和 std::unordered_set的异同

目录

1.简介

2.详细用法

2.1.初始化与构造

2.2.插入元素(insert)

2.3.查找元素(find)

2.4.删除元素(erase)

2.5.遍历元素

6.特有方法(因有序性差异)

3.自定义类型的支持

4.关键差异总结

5.总结


1.简介

        std::setstd::multiset 和 std::unordered_set 都是 C++ 标准库中用于存储元素的容器,但它们在元素唯一性有序性底层实现性能特性上有显著差异。

        先通过表格直观对比三者的核心差异:

容器底层实现有序性元素唯一性插入 / 删除 / 查找复杂度头文件典型用途
std::set平衡二叉搜索树(红黑树)有序(默认升序)唯一O(log n)<set>有序去重场景(如排序后的唯一值)
std::multiset平衡二叉搜索树(红黑树)有序(默认升序)允许重复O(log n)<set>有序允许重复场景(如统计频率前的排序)
std::unordered_set哈希表(链地址法)无序唯一平均 O (1),最坏 O (n)<unordered_set>无序去重场景(如快速判断元素是否存在)

2.详细用法

以 int 类型为例,三者的接口设计相似,但因特性不同,部分操作的行为和返回值有差异。

2.1.初始化与构造

#include <set>
#include <unordered_set>
#include <iostream>int main() {// std::set:自动去重并排序std::set<int> s = {3, 1, 4, 1, 2}; // 结果:{1, 2, 3, 4}(升序,去重)// std::multiset:允许重复,自动排序std::multiset<int> ms = {3, 1, 4, 1, 2}; // 结果:{1, 1, 2, 3, 4}(升序,保留重复)// std::unordered_set:自动去重,无序std::unordered_set<int> us = {3, 1, 4, 1, 2}; // 结果:{3,1,4,2}(顺序不确定,去重)// 范围构造(从其他容器迭代器构造)std::set<int> s2(s.begin(), s.end()); // 复制 s 的元素(有序)std::multiset<int> ms2(us.begin(), us.end()); // 复制 us 的元素并排序:{1,2,3,4}return 0;
}

std::set、std::multiset 和 std::unordered_set为什么可以直接用{}去初始化,它的原因可参考:

C++之std::initializer_list详解

2.2.插入元素(insert

插入是三者差异较明显的操作,主要体现在返回值和重复元素的处理上。

容器插入行为返回值类型说明
std::set若元素已存在则插入失败,否则成功pair<iterator, bool>second 为 true 表示插入成功
std::multiset无论元素是否存在都插入(允许重复)iterator返回指向新插入元素的迭代器
std::unordered_set若元素已存在则插入失败,否则成功pair<iterator, bool>与 set 逻辑相同,但底层是哈希操作

代码示例

// std::set 插入
auto ret_s = s.insert(5); // s 变为 {1,2,3,4,5},ret_s.second = true
auto ret_s2 = s.insert(3); // 3 已存在,ret_s2.second = false,ret_s2.first 指向已有 3// std::multiset 插入
auto it_ms = ms.insert(1); // ms 变为 {1,1,1,2,3,4},it_ms 指向新插入的 1(第二个 1 之后)// std::unordered_set 插入
auto ret_us = us.insert(5); // us 新增 5(位置不确定),ret_us.second = true
auto ret_us2 = us.insert(3); // 3 已存在,ret_us2.second = false

插入元素函数返回pair<iterator, bool>也是编程中返回值的常见用法,它的原型:

template <bool _Multi2 = _Multi, enable_if_t<!_Multi2, int> = 0>
pair<iterator, bool> insert(const value_type& _Val) {const auto _Result = _Emplace(_Val);return {iterator(_Result.first, _Get_scary()), _Result.second};
}

向容器中插入元素,若元素已存在则插入失败,返回包含结果的迭代器和标志。通过模板参数和 SFINAE 机制,它与多值容器(std::multiset)的 insert 实现(返回 iterator 而非 pair)区分开,确保不同容器的行为符合其设计语义(单值容器需返回是否插入成功,多值容器无需返回标志,因为允许重复插入)。

C++之std::enable_if

C++惯用法: 通过std::decltype来SFINAE掉表达式

利用std::set和std::unordered_set的有序可以实现判断一个序列是否有重复的元素,如:

#include <map>
#include <unordered_set>
#include <iostream>// 模板函数:支持任意key和value类型的map
template <typename K, typename V>
bool hasDuplicateValues(const std::map<K, V>& targetMap) {std::unordered_set<V> seenValues; // 记录已出现的valuefor (const auto& pair : targetMap) { // 遍历map的所有键值对// 尝试插入value,若插入失败(已存在),直接返回true(有重复)if (!seenValues.insert(pair.second).second) {return true;}}return false; // 遍历结束无重复
}// 测试示例
int main() {std::map<int, std::string> map1 = {{1, "a"}, {2, "b"}, {3, "a"}};std::map<int, int> map2 = {{1, 10}, {2, 20}, {3, 30}};std::cout << "map1是否有重复value:" << (hasDuplicateValues(map1) ? "是" : "否") << std::endl; // 输出“是”std::cout << "map2是否有重复value:" << (hasDuplicateValues(map2) ? "是" : "否") << std::endl; // 输出“否”return 0;
}

2.3.查找元素(find

查找元素是否存在,返回指向元素的迭代器;若不存在,返回 end()

  • std::set 和 std::multiset:因底层是红黑树,查找基于键的比较(有序查找)。
  • std::unordered_set:基于哈希值查找(无序)。
// std::set 查找
auto it_s = s.find(3);
if (it_s != s.end()) {std::cout << "set 找到 3" << std::endl; // 存在,输出
}// std::multiset 查找(返回第一个匹配元素)
auto it_ms = ms.find(1);
if (it_ms != ms.end()) {std::cout << "multiset 找到第一个 1" << std::endl; // 存在,输出
}// std::unordered_set 查找
auto it_us = us.find(2);
if (it_us != us.end()) {std::cout << "unordered_set 找到 2" << std::endl; // 存在,输出
}
  • std::multiset允许值重复,因此可以用equal_range来方法专门用于返回这个连续范围,其返回值是一个 pair<iterator, iterator>

#include <set>
#include <iostream>int main() {std::multiset<int> ms = {1, 3, 3, 2, 3, 4}; // 有序存储:{1,2,3,3,3,4}int target = 3;// 获取所有值为 target 的元素范围auto range = ms.equal_range(target);// 遍历范围,输出所有相同元素std::cout << "值为 " << target << " 的所有元素:";for (auto it = range.first; it != range.second; ++it) {std::cout << *it << " "; // 输出:3 3 3}// (可选)获取相同元素的数量size_t count = std::distance(range.first, range.second);std::cout << "\n数量:" << count << std::endl; // 输出:3return 0;
}

2.4.删除元素(erase

支持按值、迭代器或范围删除,差异主要体现在删除重复元素时的行为。

容器按值删除(erase(val))返回值说明
std::setsize_t(0 或 1)最多删除 1 个元素(因元素唯一)
std::multisetsize_t(删除的元素个数)删除所有值为 val 的元素(可能多个)
std::unordered_setsize_t(0 或 1)最多删除 1 个元素(因元素唯一)
// std::set 删除
size_t cnt_s = s.erase(3); // 删除 3,返回 1(s 变为 {1,2,4,5})// std::multiset 删除(删除所有 1)
size_t cnt_ms = ms.erase(1); // ms 原 {1,1,1,2,3,4},删除后 {2,3,4},返回 3// std::unordered_set 删除
size_t cnt_us = us.erase(3); // 删除 3,返回 1

2.5.遍历元素

遍历行为受 “有序性” 影响:

  • std::set 和 std::multiset:遍历结果为升序(可通过自定义比较器修改顺序)。
  • std::unordered_set:遍历结果无序(取决于哈希函数和插入顺序)。
// 遍历 set(有序)
std::cout << "set 遍历:";
for (int num : s) { std::cout << num << " "; } // 输出:1 2 4 5// 遍历 multiset(有序,可能有重复)
std::cout << "\nmultiset 遍历:";
for (int num : ms) { std::cout << num << " "; } // 输出:2 3 4// 遍历 unordered_set(无序)
std::cout << "\nunordered_set 遍历:";
for (int num : us) { std::cout << num << " "; } // 输出:1 2 4 5(顺序不确定)

6.特有方法(因有序性差异)

std::set 和 std::multiset(有序):提供范围查询方法(基于红黑树的有序特性):

  • lower_bound(val):返回第一个不小于 val 的元素迭代器。
  • upper_bound(val):返回第一个大于 val 的元素迭代器。
  • equal_range(val):返回包含所有等于 val 的元素的范围(set 中范围长度为 0 或 1,multiset 中可能更长)。
std::set<int> s = {1,3,5,7};
auto it_low = s.lower_bound(4); // 指向 5(第一个 >=4 的元素)
auto it_high = s.upper_bound(5); // 指向 7(第一个 >5 的元素)std::multiset<int> ms = {1,2,2,3};
auto range = ms.equal_range(2); // 范围为 [第一个 2, 第一个 >2 的元素),即两个 2

std::unordered_set(无序):提供哈希表相关方法:

  • load_factor():返回负载因子(元素数 / 桶数,影响哈希冲突概率)。
  • rehash(n):调整桶数为至少 n,减少哈希冲突。

3.自定义类型的支持

容器存储自定义类型时,需根据底层实现满足不同条件:

1.std::set 和 std::multiset(红黑树)

需定义比较规则(默认使用 std::less<T>,即需要 operator< 或自定义比较器),用于红黑树的排序。

#include <set>
#include <string>struct Student {std::string name;int score;
};// 自定义比较器:按分数升序(分数相同则按姓名升序)
struct CompareStudent {bool operator()(const Student& a, const Student& b) const {if (a.score != b.score) return a.score < b.score;return a.name < b.name;}
};// 声明容器时指定比较器
std::set<Student, CompareStudent> s_stu;
std::multiset<Student, CompareStudent> ms_stu;// 插入元素(会按分数自动排序)
s_stu.insert({"Alice", 90});
s_stu.insert({"Bob", 85}); // 按分数排序:Bob(85) -> Alice(90)

2.std::unordered_set(哈希表)

需定义哈希函数std::hash<T> 特化)和相等判断operator==),用于哈希表的存储和查找。

#include <unordered_set>struct Student {std::string name;int score;// 相等判断:姓名和分数都相同才视为相等bool operator==(const Student& other) const {return name == other.name && score == other.score;}
};// 特化 std::hash 提供哈希函数
namespace std {template<> struct hash<Student> {size_t operator()(const Student& s) const {size_t h1 = hash<std::string>()(s.name);size_t h2 = hash<int>()(s.score);return h1 ^ (h2 << 1); // 组合哈希值(简单实现)}};
}// 声明容器(自动使用自定义哈希和 ==)
std::unordered_set<Student> us_stu;// 插入元素(无序存储)
us_stu.insert({"Alice", 90});
us_stu.insert({"Bob", 85});

4.关键差异总结

1.元素唯一性

  • set 和 unordered_set 不允许重复元素(插入重复值会失败)。
  • multiset 允许重复元素(插入重复值会成功,保留所有副本)。

2.有序性

  • set 和 multiset 是有序容器(默认升序,可通过比较器修改),遍历结果有序。
  • unordered_set 是无序容器,遍历结果与插入顺序无关(取决于哈希函数)。

3.性能

  • set 和 multiset:插入、删除、查找的时间复杂度为 O(log n)(红黑树的平衡特性保证)。
  • unordered_set:平均时间复杂度为 O(1),但最坏情况(哈希冲突严重)为 O(n)(取决于哈希函数质量)。

4.内存开销

  • set 和 multiset:红黑树需要存储额外的指针(父、左、右孩子),内存开销较大。
  • unordered_set:哈希表需要存储桶和链表指针,内存开销与负载因子相关(负载因子过高会触发扩容)。

5.总结

  • 若需要有序且唯一的元素,且可能需要范围查询(如找大于 x 的元素):选 std::set
  • 若需要有序且允许重复的元素,且需统计或处理重复值:选 std::multiset
  • 若不需要有序,仅需快速判断元素是否存在(插入 / 查找频繁):选 std::unordered_set(性能更优)。

通过以上对比,可根据具体需求(有序性、重复值允许、性能优先级)选择最合适的容器。

相关文章:

C++之multimap:关键字分类的利器

C++ STL中 set 和 map 的区别

http://www.dtcms.com/a/540290.html

相关文章:

  • iO 拆解:从熟悉的密码模块构建
  • 2026年AEI SCI1区TOP,混合近端策略粒子群算法+公路线形优化,深度解析+性能实测
  • 英文的购物网站泉州网站建设方案详细
  • 如何建公司网站的步骤ppt做的最好的网站
  • 网站建设比较好的多少钱建企业网站需要哪些资料
  • 深圳住房和城乡建设部网站大学网站 作风建设专题
  • 电商网站建设需要活动 网站 源码
  • 如何使用Spring Cloud Gateway实现动态路由?
  • Linux Wlan 无线协议栈开发-传输层详解
  • 前端基础之《React(4)—webpack简介-编译打包优化》
  • F039 python五种算法美食推荐可视化大数据系统vue+flask前后端分离架构
  • 网站开发框架参考文献京东官方网上商城
  • Spring OXM:轻松实现Java-XML互转
  • 功能测试总结
  • 小白来学习 LVDS 差分原理及应用
  • 【Linux】网络层协议IP
  • 《Muduo网络库:TcpConnection类》
  • 网站详情页怎么做的好看的网页设计作品欣赏
  • 线扫相机上位机开发——如何提高问题排查效率
  • 计算机网络自顶向下方法10——应用层 HTTP/2 成帧 响应报文优先次序和服务器推
  • 孝感网站的建设网页设计一般一个月工资多少
  • 什么是持续集成(CI)和持续交付(CD)?测试在其中扮演什么角色?
  • 利用机器学习优化CPU调度的一些思路案例
  • Kafka 消息顺序消费深度解析:原理、实现方案与全局有序可行性分析
  • 数据结构初识,与算法复杂度
  • 网站色彩搭配中国纪检监察报社官网
  • (六)策略梯度算法 and Actor-Critic 框架
  • 基于萤火虫算法(FA)优化支持向量机(SVM)参数的分类实现
  • 【C++】C++11出来之后,到目前为止官方都做了些什么更新?
  • 公司网站建设及推广淮南网云小镇怎么样