hash_map、unordered_map和map的效率、区别和分析

hash_map、unordered_map和map的效率、区别和分析

一、前言

最近在做题的时候遇到了,就分享一下自己的心得。
hash_map、unordered_map和map的区别其实和hash_set、unordered_set和set的区别是一样的,本博客就只讲map了。

二、三者的实现区别

map

红黑树实现(二叉查找树的一种)可以实现数据log n级的插入、查找效率。

hash_map和unordered_map

这里为什么将二者一起说呢?因为hash_map和unordered_map都是用哈希表实现的,它们有什么区别吗?其实区别不大,但是推荐使用unordered_map,因为unordered_map在C++11就被录入标准库了,而hash并没有进入标准库。

说到这那到底hash_set与unordered_set哪个更好呢?实际上unordered_set在C++11的时候被引入标准库了,而hash_set并没有,所以建议还是使用unordered_set比较好,这就好比一个是官方认证的,一个是民间流传的。在编译器中,Visual Studio(当然需要支持C++11的版本)库中两个数据结构都有定义,而在gcc/g++中并不支持hash_set。
————————————————
原文链接:https://blog.csdn.net/FX677588/article/details/76400389

三、三者查询效率高低

相信很多人觉得log n的map查询复杂度已经可以了,确实log 是比较理想的时间复杂度,但是hash_map和unordered_map可以更快!因为它们都是用哈希表实现的,具体实现方式可以不用详细了解,我们都知道在哈希函数散列的过程中可能会遇到冲突,所以:

时间效率

hash_map和unordered_map的一次查询/插入时间为:
无冲突时:O(1) 有冲突时最坏为O(n)。

时间复杂度为常数级,一般更倾向于左边

map的平均时间复杂度为:log n

1.以上讨论的都是查询时间,它们的差距主要是因为它们实现的数据结构的差别,前者是哈希表,查询/插入时间几乎为常数;而红黑树的查询、插入时间都是log级别的。

2.如果是遍历(用迭代器),我觉得时间复杂度区别不大,基于哈希表的hash_map和unordered_map的begin()的查找最优是O(1)最坏是O(n),遍历时间是O(n),总时间是O(2n);而基于红黑树的map的遍历是logn(迭代器++是中序遍历),总时间复杂度为O(logn+n);这些分析都是基于对map有操作的情况下的即map.begin()可能随着map.erase(iter)或是map.add(key, value)操作而发生改变,所以综上它们的遍历时间都可以看做O(n)线性时间。

3.但是基于哈希表结构的容器构造速度慢,占用的空间也比大。

其实具体选择哪个容器还得综合考虑三个因素:查找速度, 数据量, 内存使用。

如果想详细了解三者更详细的时间效率对比:
https://blog.csdn.net/stpeace/article/details/81283650

在较大量的数据测试下,我们知道
时间效率:unordered_map最好,hash_map其次,而map效率最差。

三者使用选择

由上面的分析我们知道hash_map和unordered_map应该选择录入标准库的unordered_map。

那unordered_map和map如何选择呢?
从上面的分析我们知道unordered_map时间更快,当然选择unordered_map了,可以这么选,但对于单次log级(map) 可能小于常数级(unordered_map),所以稳定性是map比较好,但是对于有些题目要求的查询次数比较频繁时,我们应该unordered_map优先。

不懂?看下面2020 蓝桥杯大学 B 组省赛模拟赛(一) F:寻找重复项

例题:

在这里插入图片描述
样例输入:

2 2 9
  • 1

输出:

4
  • 1

有人会说用一个标记数组来查询某数是否出现只需要O(1)不香吗
还要hash_map、unordered_map和map吗?

你再仔细看看数据范围109数组开不了这么大,所以不香了。
而且有时候我们查询的可能不是一个整数,所以map还是很香的嘛。
下面的代码我们都会:

#include<iostream>
#include<cstdio> 
#include<cmath>
#include<map> 
#define ll long long int
using namespace std;
map<ll,int> book;
int main(void)
{
	int i=0;
	ll s=1ll;
	ll a,b,c;
	scanf("%lld %lld %lld",&a,&b,&c);
	
	while(true)
	{
		book[s]=1;
		s=(s*a%c+s%b%c)%c;
		i++;
		if(book[s]) break;
		if(i>2000000) break;
	}
	if(i>2000000) printf("-1\n");
	else printf("%d\n",i);
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

超时了?!
我们可以在上面的代码看到,我们每计算一个值就要查询一次这个值是否存在,查询次数频繁。
我们就放弃map改用unordered_map。
unordered_map实现代码:

#include<iostream>
#include<cstdio> 
#include<cmath>
#include<unordered_map> 
#define ll long long int
using namespace std;
unordered_map<ll,int> book;
int main(void)
{
	int i=0;
	ll s=1ll;
	ll a,b,c;
	scanf("%lld %lld %lld",&a,&b,&c);
	
	while(true)
	{
		book[s]=1;
		s=(s*a%c+s%b%c)%c;
		i++;
		if(book[s]) break;
		if(i>2000000) break;
	}
	if(i>2000000) printf("-1\n");
	else printf("%d\n",i);
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

其实map一般够的,map不行再改用unordered_map。

编译器报错解决方法

由于unordered_map是c++11里的,很多编译器还是使用的比较老的版本比如C++98,我们拿devc++为例,我们如何让它支持c++11呢?
在编译时假如-std=c++11,具体请点击链接

其实目前蓝桥杯不支持c++11,故加了上面的蓝桥杯还是不让用的

猜你喜欢

转载自blog.csdn.net/csdn9990/article/details/112281363