【C++】map/set 与 unordered_map/set 的核心区别与选型

引言

本文深度解析了C++中map/setunordered_map/unordered_set这两类关联容器的核心区别 。基于红黑树实现的有序容器提供稳定的O(log n)操作复杂度与元素自动排序,适用于需要顺序遍历和范围查询的场景 ;而基于哈希表实现的无序容器则在平均情况下提供O(1)的极致访问速度,适合对单点操作性能要求极高且不关心元素顺序的应用



目录

一、核心区别

特性 map / set unordered_map / unordered_set
底层数据结构 红黑树 哈希表
元素排序 元素自动排序(按 key 升序) 元素无序存储(顺序不确定)
时间复杂度 增删查:O(log n) 平均情况增删查:O(1) 最坏情况增删查:O(n)
迭代器功能 提供正向反向 迭代器 迭代顺序是确定的、有序的 通常只提供正向 迭代器 迭代顺序是不确定的、无序的
是否需要哈希函数 不需要,只需定义 < 比较运算符 必须为 key 定义有效的哈希函数
自定义 Key 要求 需要定义 < 运算符或传入自定义比较器 需要定义 == 运算符和 std::hash 特化(或自定义哈希函数)
内存占用 通常较高(树节点结构开销) 通常较低,但存在哈希桶的数组开销和可能的负载因子浪费
适用场景 1. 需要元素有序 遍历 2. 需要按顺序进行范围查询 3. 需要稳定性和可预测的性能 1. 对单点访问速度 要求极高 2. 不关心元素顺序 3. 哈希函数分布均匀,能避免最坏情况
  • 选择 map/set:你需要元素始终保持有序,或者需要频繁地进行范围查询(如"找出所有年龄在20到30之间的人"),或者你无法为一个复杂的 key 设计出高效的、分布均匀的哈希函数。

  • 选择 unordered_map/unordered_set :你的主要操作是快速的单点查找、插入和删除,并且元素的顺序完全无关紧要。在大多数情况下,如果你的 key 是基础类型或标准库字符串,并且不要求顺序,unordered_ 版本会提供更好的性能。


二、性能对比

说到一个容器的性能,我们最关心的实际就是该容器增删查改的效率。我们可以通过下列代码测试set容器和unordered_set容器insert、find以及erase的效率。

效率的对比肯定是需要对比执行相应操作的时间,这里我们来介绍一下相关的类型和函数:

  1. cloxk_t
    clock_t 是一个算术类型(通常是 longlong long),用于存储处理器时间值。

  2. clock()
    clock() 返回的值单位是 时钟滴答,要转换为秒,需要除以CLOCKS_PER_SEC宏。

clock() 函数 + clock_t 类型的主要作用:测量程序使用的 CPU 时间(处理器时间)。

cpp 复制代码
//红黑树/哈希表的性能对比
#include<iostream>
#include<vector>

#include<time.h>
#include<set>
#include<unordered_set>

using namespace std;

int main()
{
	int N = 100000;
	vector<int> v;
	v.reserve(N);
	srand((unsigned int)time(NULL));
	//随机生成N个数字
	for (int i = 0; i < N; i++)
	{
		v.push_back(rand());
	}

	/****************插入效率测试****************/
	//将这N个数插入set容器
	set<int> s;
	clock_t begin1 = clock();
	for (auto e : v)
	{
		s.insert(e);
	}
	clock_t end1 = clock();

	//将这N个数插入unordered_set容器
	unordered_set<int> us;
	clock_t begin2 = clock();
	for (auto e : v)
	{
		us.insert(e);
	}
	clock_t end2 = clock();

	//分别输出插入set容器和unordered_set容器所用的时间
	cout << "set insert: " << end1 - begin1 << endl;
	cout << "unordered_set insert: " << end2 - begin2 << endl;

	/****************查找效率测试****************/
	//在set容器中查找这N个数
	clock_t begin3 = clock();
	for (auto e : v)
	{
		s.find(e);
	}
	clock_t end3 = clock();

	//在unordered_set容器中查找这N个数
	clock_t begin4 = clock();
	for (auto e : v)
	{
		us.find(e);
	}
	clock_t end4 = clock();

	//分别输出在set容器和unordered_set容器中查找这N个数所用的时间
	cout << "set find: " << end3 - begin3 << endl;
	cout << "unordered_set find: " << end4 - begin4 << endl;

	/****************删除效率测试****************/
	//将这N个数从set容器中删除
	clock_t begin5 = clock();
	for (auto e : v)
	{
		s.erase(e);
	}
	clock_t end5 = clock();

	//将这N个数从unordered_set容器中删除
	clock_t begin6 = clock();
	for (auto e : v)
	{
		us.erase(e);
	}
	clock_t end6 = clock();

	//分别输出将这N个数从set容器和unordered_set容器中删除所用的时间
	cout << "set erase: " << end5 - begin5 << endl;
	cout << "unordered_set erase: " << end6 - begin6 << endl;
	return 0;
}

随机数的生成代码+原理解析<----------请点击

当N为100000时,set容器和unordered_set容器增删查改的效率差异并不大,在Debug版本下的测试结果如下:

在Release版本下,set容器和unordered_set容器对1000个数做增删查改操作所用的时间更是被优化到了接近0毫秒。

注意: 这里给出的N值仅作参考,在不同的测试环境下可能不同。


总结

根据测试结果可以得出以下结论:

  • 当处理数据量小时,map/set容器与unordered_map/unordered_set容器增删查改的效率差异不大。
  • 当处理数据量大时,map/set容器与unordered_map/unordered_set容器增删查改的效率相比,unordered系列容器的效率更高。
    因此,当处理数据量较大时,建议选用对应的unordered_set/unordered_map容器。

注意:当需要存储的序列为有序时,应该选用map/set容器。


✨ 坚持用 清晰易懂的图解 + 代码语言, 让每个知识点都 简单直观 !

🚀 个人主页不呆头 · CSDN

🌱 代码仓库不呆头 · Gitee

📌 专栏系列

💬 座右铭 : "不患无位,患所以立。"

相关推荐
郭涤生1 小时前
布隆过滤器
c++
喵了meme1 小时前
C语言实战4
c语言·开发语言
码界奇点1 小时前
Python从0到100一站式学习路线图与实战指南
开发语言·python·学习·青少年编程·贴图
智者知已应修善业1 小时前
【求中位数】2024-1-23
c语言·c++·经验分享·笔记·算法
9ilk1 小时前
【C++】--- 特殊类设计
开发语言·c++·后端
sali-tec2 小时前
C# 基于halcon的视觉工作流-章68 深度学习-对象检测
开发语言·算法·计算机视觉·重构·c#
生骨大头菜4 小时前
使用python实现相似图片搜索功能,并接入springcloud
开发语言·python·spring cloud·微服务
绝不收费—免费看不了了联系我4 小时前
Fastapi的单进程响应问题 和 解决方法
开发语言·后端·python·fastapi
消失的旧时光-19434 小时前
深入理解 Java 线程池(二):ThreadPoolExecutor 执行流程 + 运行状态 + ctl 原理全解析
java·开发语言
咖啡续命又一天4 小时前
Trae CN IDE 中 Python 开发的具体流程和配置总结
开发语言·ide·python·ai编程