STL中的map和unordered_map选择

基本概念

map

map在底层使用了红黑树来实现,在头文件map中我们可以找到这样一句话

ordered red-black tree of {key, mapped} values, unique keys

红黑树是每个节点都带有颜色属性的二叉查找树,颜色或红色或黑色。在二叉查找树强制一般要求以外,红黑树则还要满足以下五个性质:

性质1 节点是红色或黑色。

性质2 根节点是黑色。

性质3 每个叶节点(NIL节点,空节点)是黑色的。

性质4 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)

性质5 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。

这些约束强制了红黑树的关键性质: 从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。一个节点的插入需要牵涉到树的旋转和变色等操作,这里不再关注其细节部分。

unordered_map

unordered_map是C++11标准中新加入的容器,它的底层是使用hash表的形式来完成映射的功能,在头文件unordered_map中有说明

hash table of {key, mapped} values, unique keys

hash表在底层则是通过hash算法将key的值直接映射到数组上,这样不免会产生hash碰撞的问题,数组的每个单元对应一个bucket,即hash桶,桶可采用链表或是树等多种方式构成。如Java中的HashMap的bucket在1.8更新时就从链表改成了红黑树。

二者的效率对比

二者既然有十分相似的功能,不免会在选择时产生迷茫,到底哪一个数据结构能够满足要求,并且执行的速度又足够快。

实验设计

无序情况下的随机存储查询对比

for (int xx = 1000; xx != 11000; xx += 1000)
	{
		vector<int> t;
		vector<int> ti;
		int b = 0;
		map<int, int> a;
		for (int n = 0; n != 10000; n++)
		{
			srand(time(nullptr));
			vector<int> key;
			vector<int> val;
			for (int i = 0; i != xx; i++)
			{
				key.emplace_back(rand());
				val.emplace_back(rand());
			}
			LARGE_INTEGER insertstart;
			QueryPerformanceCounter(&insertstart);
			for (int i = 0; i != xx; i++)
			{
				a[key[i]] = val[i];
			}
			LARGE_INTEGER insertend;
			QueryPerformanceCounter(&insertend);
			ti.emplace_back(insertend.QuadPart - insertstart.QuadPart);
			vector<int> find;
			for (int i = 0; i != 10000; i++)
			{
				find.emplace_back(key[rand() % key.size()]);
			}

			LARGE_INTEGER start;
			QueryPerformanceCounter(&start);
			for (auto i :find)
			{
				b += a[i];
			}
			LARGE_INTEGER end;
			QueryPerformanceCounter(&end);
			t.emplace_back(end.QuadPart - start.QuadPart);
			a.clear();
		}
		cout << b << endl;
		LARGE_INTEGER f;
		QueryPerformanceFrequency(&f);
		double avg = static_cast<double>(accumulate(t.begin(), t.end(), 0)) / t.size();
		double avginsert = static_cast<double>(accumulate(ti.begin(), ti.end(), 0)) / ti.size();
		int longestdur = *max_element(t.begin(), t.end());
		int shortestdur = *min_element(t.begin(), t.end());
		cout << "rbt" << xx << endl;
		cout << "avg:" << avg / f.QuadPart * 1000 << " max-min:" << static_cast<double> (longestdur - shortestdur) / avg << endl;
		longestdur = *max_element(ti.begin(), ti.end());
		shortestdur = *min_element(ti.begin(), ti.end());
		cout << "insert time:" << avginsert / f.QuadPart * 1000 <<" max:" << static_cast<double> (longestdur) / f.QuadPart*1000<<" min:"<< static_cast<double> (shortestdur) / f.QuadPart * 1000 << endl;
		double var = 0.0;
		for (auto i : t)
		{
			var += (i - avg)*(i - avg);
		}
		var = var / (t.size() - 1);
		cout << "var:" << var / f.QuadPart * 1000 << endl;
	}

该程序为VC的程序,使用QueryPerformanceCounter来获取执行时间,本次实验使用10000次存n个随机value,取10000个随机key的平均时间作为实验数据,n从1000至10000每1000取一点,从10000至100000每10000取一点。可将map修改为unordered_map来获得unordered_map的结果。

结论

绿线为map的随机访问时间,红线为unordered_map的随机访问时间,可以看出,使用hash表的unordered_map在随机访问上的优势极其明显。与理论值类似,红黑树的随机访问平均时间呈对数状。 

同样的,在插入时间上也有类似的结论,二者的插入时间都呈对数增长,map的时间明显高于 unordered_map。

因此如果在不涉及顺序并且能够设计出较好的hash算法(碰撞较少)的情况下,unordered_map是最好的选择。但是如果考虑顺序的因素,仍旧需要选择map,链表数组等线性结构在排序方面是优于二叉树的,但是当涉及到动态插入的情况时,这些线性结构显得力不从心,以下是在红黑树和链表在排序和动态插入时对比的结果使用的数据为100000个

rbt init time:0.0614347
list init time:0.0227488
rbt insert time:0.0629764
list insert time:60.7506

可以看出,链表排序速度较快,但是有序插入时速度远远低于红黑树,因此在考虑有序的动态数据结构时,红黑树仍旧是较好的选择。

### 回答1: C++ STL是C++标准库的一部分,包含了许多常用的数据结构和算法,如vector、list、map、set等。STL的设计目的是提供高效、可靠、通用的数据结构和算法,使得程序员可以更加方便地编写高质量的代码。 map和unordered_mapSTL的两种关联容器,它们都可以用于存储键值对。map是一种有序的关联容器,它使用红黑树实现,可以快速地查找、插入和删除元素,但是它的空间复杂度较高。unordered_map是一种无序的关联容器,它使用哈希实现,可以在常数时间内查找、插入和删除元素,但是它的空间复杂度较低。 在使用map和unordered_map时,需要注意它们的特点和适用场景。如果需要有序地存储键值对,并且需要快速地查找、插入和删除元素,那么应该选择map;如果不需要有序地存储键值对,并且需要在常数时间内查找、插入和删除元素,那么应该选择unordered_map。 ### 回答2: C++ STL(标准模板库)是C++标准库的一部分,提供了一组标准的库函数和容器类,能够帮助C++开发者提高代码复用性,减少代码编写时间。其map和unordered_mapSTL常用的关联容器,用于存储键值对。 map是有序的关联容器,它存储键值对,并且根据键排序,使用红黑树实现,插入/查找/删除操作的时间复杂度均为log(N)。map可以被用于实现数据结构,例如有序映射和堆积。map对于需要手动排序的问题十分实用,因为它会自动维护键值的顺序。 unordered_map是哈希实现的关联容器,它同样存储键值对,但是不会对键进行排序,插入/查找/删除的平均时间复杂度为常数级别,实际上操作速度更快。一般来说,unordered_map速度更快,但是由于哈希的不确定性,其效率可能会受到键分布的影响。 两个容器都可以通过类似于数组的方式访问元素,但是map访问方式是map[key],而unordered_map的访问方式为unordered_map[key]。对于元素的添加,map需要使用insert函数,而unordered_map使用emplace或insert函数均可。 在使用STL的容器时,选择合适的数据结构非常重要。如果需要存储有序的数据并且希望通过键来快速访问数据,可以选择map。但是,如果只关心快速访问和查找数据,可以选择unordered_map。需要注意的是,在许多情况下,具体的使用场景会影响选择的容器类型。 ### 回答3: C++ STL(Standard Template Library)是C++标准库的一部分,包含了许多模板类、函数和算法,大大提高了C++程序的开发效率。其map和unordered_map是两个重要的容器类。 map是一个关联容器,它将键值对映射到一个有序的序列,其键是唯一的。map的底层实现是红黑树,因此它具有快速的查找和插入操作。map是按照键的自然顺序进行排序的,默认按照小于号(<)进行比较。如果需要按照其他方式进行排序,可以自定义比较函数。 unordered_map也是一个关联容器,它将键值对映射到一个无序的序列,其键是唯一的。unordered_map的底层实现是哈希,因此它具有快速的查找和插入操作。unordered_map的元素是无序的,因此不能像map那样直接遍历。如果需要按照键的自然顺序进行遍历,可以将unordered_map的元素复制到一个vector,然后对vector进行排序。 map和unordered_map都支持以下操作: 1. 插入元素:使用插入函数(insert)或者下标运算符[]来插入元素。 2. 删除元素:使用删除函数(erase)来删除指定位置或者指定键的元素。 3. 查找元素:使用find函数来查找指定键的元素,如果找到了就返回对应元素的迭代器,否则返回尾迭代器。 4. 遍历元素:可以使用迭代器(iterator)来遍历容器的所有元素。 在使用map和unordered_map时,需要注意以下问题: 1. 如果键是自定义类型,需要重载==运算符和小于号(<)运算符,以便能够正确比较元素。 2. 在使用unordered_map时,需要定义一个哈希函数,以便将元素散列到桶。 3. 在插入元素时,如果键已经存在,会直接覆盖原有元素。 4. 在删除元素时,需要注意迭代器过期的问题,删除元素后,迭代器可能会失效,不能再使用。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值