C++ | unordered_map与unordered_set的用法指南

目录

前言

一、unordered_set

1、简介

2、构造相关函数

3、容量相关函数

4、修改与查找相关接口

5、迭代器

二、unordered_map

1、简介

2、构造相关函数

3、容量相关接口

4、迭代器、查找与修改相关接口

5、方括号接口

三、红黑树系列与哈希系列对比


前言

        unordered_map与unordered_set是C++11推出的关联式容器,他们的作用与我们前面学习的map与set差不多一样,唯一不同的是这两个容器相比于map/set数据是无序的;我们从名字也可以看出来,具体上,实际上底层map/set是用的红黑树,而unordered_map/unordered_set是使用哈希来实现的;

一、unordered_set

1、简介

        unordered_set也是一种关联式容器,作用主要与我们前面学过的set相同,我们首先看这个类的声明;具体如下;

        我们可以看到这个类有四个模板参数,其中后三个都有默认的模板参数,参数一是存入Key的数据类型,与我们之前的set相同;第二个参数是哈希函数,这个以及后面的两个都可以不填,哈希函数会在后面一张写实现的时候进行介绍;

2、构造相关函数

        unordered_set的构造相关函数我们用的最多的还是如下几个;分别为无参构造、迭代器区间构造、拷贝构造;当然set也支持赋值重载;

void test_unordered_set1()
{
	// 无参默认构造
	unordered_set<int> s1;
	// 区间构造
	int arr[] = { 1,2,3,4,5,6 };
	unordered_set<int> s2(arr, arr + 5);
	// 拷贝构造
	unordered_set<int> s3(s2);

	// 赋值重载
	s2 = s1;

}

3、容量相关函数

        容量相关函数还是如下三个,这三个我们可以一看就懂了,主要还是第一个与第二个函数,第一个是查看容器是否为空,第二个是容器中储存元素个数; 

4、修改与查找相关接口

        修改相关接口主要是如下四个为主,其他的需要我们拥有右值引用相关知识,暂可不了解;查找主要以find接口为主来学习;

void test_unordered_set2()
{
	unordered_set<int> s1;
    // 插入接口
	s1.insert(2); // 返回值类型:pair<iterator,bool>
	s1.insert(5);
	s1.insert(3);
	s1.insert(1);
	s1.insert(7);
    // 删除接口
	s1.erase(3); // 返回值类型:size_type

	// 查找 find
	s1.find(5);  // 返回值是查找值的迭代器,没找到返回end()
	// 查找个数(在unordered_set中要么返回1要么返回0,因为会去重,unordered_multiset中有意义)
	cout << s1.count(3) << endl;

	// 清空s1
	s1.clear();
	// 交换
	unordered_set<int> s2;
	s1.swap(s2);
}

        关于insert的返回值是一个pair,该结构的第二个为bool值,记录是否插入成功,第一个记录插入数据迭代器的位置; 

5、迭代器

        注意,哈希版本的set与map这里都是单向迭代器,因此没有反向迭代器的概念;与set最大的不同是哈希版本是无序的;

void test_unordered_set3()
{
	unordered_set<int> s1;
	s1.insert(2); 
	s1.insert(5);
	s1.insert(3);
	s1.insert(1);
	s1.insert(7);

	// 迭代器
	unordered_set<int>::iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;

	// 范围for
	for (auto& e : s1)
	{
		cout << e << " ";
	}
	cout << endl;
}

        还有一些别的函数方法在我们后面模拟实现以后自然会用了,所以本文暂不介绍; 

二、unordered_map

1、简介

        同样unordered_map与map最大区别是无序性,其中map底层是红黑树,而unordered_map底层是哈希;其类声明也如下所示;最大的不同是,这里有五个模板参数,多了一个T,因为unordered_map同样也是KeyValue结构;因此多了一个T,也即是Value;

2、构造相关函数

        构造相关我们仅仅只需要会使用默认构造,范围构造,拷贝构造即可,因为大部分都是使用这三个构造;赋值我们仅仅需要会使用第一个拷贝赋值即可;其他有关右值引用知识得到后面了解;

void test_unordered_map1()
{
	// 默认构造
	unordered_map<int, int> m1;
	// 区间构造
	int arr[] = { 1,2,3,4,5,6,7 };
	unordered_map<int, int> m2;
	for (auto e : arr)
	{
		m2.insert(make_pair(e, e));
	}
	// 拷贝构造
	unordered_map<int, int> m3(m2);
	// 赋值重载
	m2 = m1;
}

3、容量相关接口

        容量相关接口非常简单,size是返回容器中元素个数,max_size是返回容器最多能存多少个元素(没啥用),empty是查看容器是否为空;

4、迭代器、查找与修改相关接口

        这里跟前面unordered_set差不多,我们就融合到一起讲解了,大体用法也差不多;没什么学习成本;

void test_unordered_map2()
{
	// 去重
	unordered_map<int, int> m1;
	// 插入接口,一般为键值对KV结构
	m1.insert(make_pair(3, 3)); // 返回值:pair<iterator,bool>
	m1.insert(make_pair(7, 7));
	m1.insert(make_pair(1, 1));
	m1.insert(make_pair(5, 5));
	m1.insert(make_pair(3, 3));

	// 查找接口
	unordered_map<int, int>::iterator it1 = m1.find(5); // 返回5位置的迭代器

	// 删除接口 返回值为 size_type (删除元素的个数)
	m1.erase(1);  // 删除key为1的键值对

	// 迭代器遍历
	unordered_map<int, int>::iterator it2 = m1.find(5);
	while (it2 != m1.end())
	{
		cout << it2->first << ": " << it2->second << endl;
		it2++;
	}
	cout << endl;
	// 范围for自然也就可以了
	for (auto& e : m1)
	{
		cout << e.first << ": " << e.second << endl;
	}
	cout << endl;

	// 交换
	unordered_map<int, int> m2;
	m1.swap(m2);
	// 清空
	m2.clear();
}

5、方括号接口

        与map一样,unordered_map也有对方括号进行重载,而且这里的方括号与map一样,功能多样化,可以插入数据,也可以修改键值对的value,还可以查找某个键值对的value;

void test_unordered_map3()
{
	unordered_map<int, int> m1;
	m1.insert(make_pair(3, 3));
	m1.insert(make_pair(7, 7));
	m1.insert(make_pair(1, 1));
	m1.insert(make_pair(5, 5));
	m1.insert(make_pair(3, 3));
	// 查找
	cout << m1[3] << endl;
	// 修改
	m1[3]++;
	// 查找
	cout << m1[3] << endl;

}

三、红黑树系列与哈希系列对比

        我们发现set/map与unordered_set/unordered_map在使用方面似乎没有什么区别,我们的红黑树系列会将数据进行排序;接下来我会从两者之间的速度进行比较;测试代码如下;

// 测试代码
void test2()
{
	const size_t N = 100000;
	unordered_set<int> us;
	set<int> s;

	vector<int> v;
	v.reserve(N);
	srand(time(0));
	for (size_t i = 0; i < N; ++i)
	{
		//v.push_back(rand());    // 随机 + 大量重复
		//v.push_back(rand()+i);  // 随机 + 部分重复
		v.push_back(i);           // 有序
	}

	size_t begin1 = clock();
	for (auto e : v)
	{
		s.insert(e);
	}
	size_t end1 = clock();
	cout << "set insert:" << end1 - begin1 << endl;

	size_t begin2 = clock();
	for (auto e : v)
	{
		us.insert(e);
	}
	size_t end2 = clock();
	cout << "unordered_set insert:" << end2 - begin2 << endl;


	size_t begin3 = clock();
	for (auto e : v)
	{
		s.find(e);
	}
	size_t end3 = clock();
	cout << "set find:" << end3 - begin3 << endl;

	size_t begin4 = clock();
	for (auto e : v)
	{
		us.find(e);
	}
	size_t end4 = clock();
	cout << "unordered_set find:" << end4 - begin4 << endl << endl;

	cout << "set 插入元素个数" << s.size() << endl;
	cout << "unordered_set 插入元素个数" << us.size() << endl << endl;

	size_t begin5 = clock();
	for (auto e : v)
	{
		s.erase(e);
	}
	size_t end5 = clock();
	cout << "set erase:" << end5 - begin5 << endl;

	size_t begin6 = clock();
	for (auto e : v)
	{
		us.erase(e);
	}
	size_t end6 = clock();
	cout << "unordered_set erase:" << end6 - begin6 << endl << endl;
}

我们分别对三种类型的数据进行比较;结果如下

        我们不难发现,综合来讲,我们的unordered系类的效率明显远远大于红黑树系列,因为我们unordered系列增删查改的综合效率为O(1);而红黑树的增删查改的综合效率为

O(log N);为什么能做到O(1),在我们探究底层原理以后我们就会有大体的认识;

猜你喜欢

转载自blog.csdn.net/Nice_W/article/details/132009263