【C++之unordered_set和unordered_map的介绍与应用】

news2024/12/23 13:19:07

C++学习笔记---024

  • C++之unordered_set和unordered_map的介绍与应用
    • 1、unordered_set和unordered_map的简单介绍
      • 1.1、unordered_set和unordered_map的基本概念
      • 1.2、unordered_set和unordered_map的基本特性
    • 2、unordered_set的基本操作
      • 2.1、unordered_set的定义
      • 2.2、unordered_set的基本接口使用
      • 2.3、unordered_multiset的基本介绍
    • 3、unordered_map的基本操作
      • 3.1、unordered_map的定义
      • 3.2、unordered_map的基本接口使用
      • 3.3、unordered_multimap的基本介绍
    • 4、map/set 和 unordered_map/unordered_set的区别
      • 4.1、性能测试
      • 4.2、总结

C++之unordered_set和unordered_map的介绍与应用

前言:
前面篇章学习了C++对于AVL树的知识认识和了解,接下来继续学习,C++的unordered_set和unordered_map等知识。
/知识点汇总/

1、unordered_set和unordered_map的简单介绍

1.1、unordered_set和unordered_map的基本概念

unordered_set 和 unordered_map 是 C++ 标准库中的两个关联容器,它们提供了基于哈希表的快速查找和插入操作。

unordered_set 是一个无序集合,它包含唯一的元素。换句话说,unordered_set 中的每个元素都是唯一的,并且没有重复。与 set 不同,unordered_set 不保证元素的排序,但通常提供了更快的插入和查找操作,因为它基于哈希表实现。
unordered_map 是一个无序关联数组(或哈希表),它存储键值对,并且每个键都是唯一的。与 map 不同,unordered_map 不保证元素的排序,但通常提供了更快的插入、删除和查找操作。

1.2、unordered_set和unordered_map的基本特性

unordered_set的基本特性:

1、unordered_set是不按特定顺序存储键值的关联式容器,其允许通过键值快速的索引到对应的元素。
2、在unordered_set中,元素的值同时也是唯一地标识它的key。
3、在内部,unordered_set中的元素没有按照任何特定的顺序排序,为了能在常数范围内找到指定的key,unordered_set将相同哈希值的键值放在相同的桶中。
4、unordered_set容器通过key访问单个元素要比set快,但它通常在遍历元素子集的范围迭代方面效率较低。
5、它的迭代器至少是前向迭代器。

unordered_map的基本特性:

1、unordered_map是存储<key, value>键值对的关联式容器,其允许通过keys快速的索引到与其对应的value。
2、在unordered_map中,键值通常用于惟一地标识元素,而映射值是一个对象,其内容与此键关联。键和映射值的类型可能不同。
3、在内部,unordered_map没有对<kye, value>按照任何特定的顺序排序, 为了能在常数范围内找到key所对应的value,unordered_map将相同哈希值的键值对放在相同的桶中。
4、unordered_map容器通过key访问单个元素要比map快,但它通常在遍历元素子集的范围迭代方面效率较低。
5、unordered_maps实现了直接访问操作符(operator[]),它允许使用key作为参数直接访问value。
它的迭代器至少是前向迭代器。

2、unordered_set的基本操作

2.1、unordered_set的定义

void test_unordered_set1()
{
	// 构造一个空壳的us1的unordered_set的容器
	unordered_set<int> us1;

	// 插入几个值
	us1.insert(1);
	us1.insert(2);
	us1.insert(3);
	us1.insert(4);

	// 拷贝构造
	unordered_set<int> us2(us1);

	// 迭代器区间构造
	unordered_set<int> us3(us2.begin(), us2.end());

	// for循环打印一下
	for (auto& e : us3)
	{
		cout << e << " ";
	}
	cout << endl;
}

2.2、unordered_set的基本接口使用

void test_unordered_set2()
{
	// 先构造一个空的容器
	unordered_set<int> us1;

	// 插入元素(只有这一种插入法)
	us1.insert(1);
	us1.insert(2);
	us1.insert(3);
	us1.insert(1);
	us1.insert(4);
	us1.insert(5);

	// 遍历容器第一种方法:迭代器遍历
	unordered_set<int>::iterator it = us1.begin();
	while (it != us1.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl; // 1 2 3 4 5

	// 遍历容器第二种方法:for循环
	for (auto& e : us1)
	{
		cout << e << " ";
	}
	cout << endl; // 1 2 3 4 5

	// 删除元素的方式一:直接找到值进行删除
	us1.erase(1);

	// 删除元素的方法二:利用迭代器进行删除
	unordered_set<int>::iterator pos = us1.find(2);
	if (pos != us1.end())
	{
		us1.erase(pos);
	}

	// 打印一下
	for (auto& e : us1)
	{
		cout << e << " ";
	}
	cout << endl; // 3 4 5

	// 判断容器是否为空
	cout << us1.empty() << endl; // 0

	// 获取值为3的个数
	cout << us1.count(3) << endl; // 1

	// 查看当前容器的容量
	cout << us1.size() << endl; // 3

	// 交换数据
	unordered_set<int> tmp{99, 88, 77, 66};
	us1.swap(tmp);

	// 打印一下
	for (auto& e : us1)
	{
		cout << e << " ";
	}
	cout << endl; // 99 88 77 66 

	// 打印一下
	for (auto& e : tmp)
	{
		cout << e << " ";
	}
	cout << endl; // 3 4 5

	// 清空
	us1.clear();

	// 打印一下
	for (auto& e : us1)
	{
		cout << e << " ";
	}
	cout << endl;  //   
}


2.3、unordered_multiset的基本介绍

大致实现的功能与unordered_map相同,但唯一不同的一点是在于这个多功能的set是允许值进行重复的!

这个多功能的set是相较于普通set来讲的count函数是返回的个数,而普通set的count函数是如果存在则返回1,不存在则返回0。
这个多功能set相较于普通set来讲的find函数是返回底层哈希表中第一个找到的键值为val的元素的迭代器,而普通set则是返回简单的key。

void test_unordered_set3()
{
	unordered_multiset<int> ums1;
	ums1.insert(1);
	ums1.insert(2);
	ums1.insert(4);
	ums1.insert(3);
	ums1.insert(1);
	ums1.insert(5);
	ums1.insert(2);
	ums1.insert(7);

	for (auto& e : ums1)
	{
		cout << e << " ";
	}
	cout << endl;  // 1 1 2 2 3 4 5 7
}

3、unordered_map的基本操作

3.1、unordered_map的定义

void test_unordered_map1()
{
	// 构造一个空的key为int,value为double的unordered_map
	unordered_map<int, double> um1;

	// 给um1赋上值
	um1.insert(make_pair(1, 1.1));
	um1.insert(make_pair(2, 2.2));
	um1.insert(make_pair(3, 3.3));
	um1.insert(make_pair(4, 4.4));

	// 拷贝构造
	unordered_map<int, double> um2(um1);

	// 迭代器区间拷贝um2的一段
	unordered_map<int, double> um3(um2.begin(), um2.end());

	// for循环打印一下um3,um3没问题则um1和um2都没问题
	for (auto& e : um3)
	{
		cout << e.first<< "=>" << e.second << " ";
	}
	cout << endl;
}

3.2、unordered_map的基本接口使用

void test_unordered_map2()
{
	// 构造一个空的key为int,value为string的unordered_map
	unordered_map<int, string> um1;
	
	// 插入方法一:构造匿名对象插入
	um1.insert(pair<int, string>(1, "111"));
	um1.insert(pair<int, string>(2, "222"));
	um1.insert(pair<int, string>(3, "333"));

	// 插入方法二:调用make_pair插入
	um1.insert(make_pair(4, "444"));
	um1.insert(make_pair(5, "555"));
	um1.insert(make_pair(6, "666"));

	// 插入方法三:用operator[]
	um1[7] = "777";
	um1[8] = "888";
	um1[9] = "999";
	um1[10] = "000";

	// 遍历方式一:利用迭代器进行遍历打印
	//unordered_map<int, string>::iterator it = um1.begin();
	auto it = um1.begin();
	while (it != um1.end())
	{
		cout << (*it).first << "=>" << (*it).second << " ";
		++it;
	}
	cout << endl; // 1=>111 2=>222 3=>333 4=>444 5=>555 6=>666 7=>777 8=>888 9=>999 10=>000

	// 遍历方法二:利用for循环进行遍历打印
	for (auto& e : um1)
	{
		cout << e.first<< "=>" << e.second << " ";
	}
	cout << endl; // 1=>111 2=>222 3=>333 4=>444 5=>555 6=>666 7=>777 8=>888 9=>999 10=>000

	// 删除操作1:根据键值对key删除
	um1.erase(5);
	// 删除操作2:根据迭代器进行删除
	unordered_map<int, string>::iterator rit = um1.find(7); // 顺带使用键值对key就可以用find函数了
	if (rit != um1.end())
	{
		um1.erase(rit);
	}
	// 遍历打印一下,用for循环方便快捷一点
	for (auto& e : um1)
	{
		cout << e.first << "=>" << e.second << " ";
	}
	cout << endl; // 1=>111 2=>222 3=>333 4=>444 6=>666 8=>888 9=>999 10=>000

	// 修改键值对:通过find获得迭代器进行修改
	auto pos = um1.find(1);
	if (pos != um1.end())
	{
		pos->second = "11/11";
	}

	// 修改键值对:通过operator[]运算符重载进行修改
	um1[2] = "22/22";

	// 打印一下
	for (auto& e : um1)
	{
		cout << e.first << "=>" << e.second << " ";
	}
	cout << endl; // 1=>11/11 2=>22/22 3=>333 4=>444 6=>666 8=>888 9=>999 10=>000

	// 判空
	cout << um1.empty() << endl; // 0 -- 不空

	// 计算容器的大小
	cout << um1.size() << endl; // 8个

	// 计算容器中键值对的大小
	cout << um1.count(3) << endl; // 1

	// 交换两容器中的数据
	unordered_map<int, string> tmp{{11, "123"}, { 22, "345" }};
	um1.swap(tmp);
	for (auto& e : tmp)
	{
		cout << "tmp=>" << " ";
		cout << e.first << "=>" << e.second << " ";
	}
	cout << endl; // tmp=> 1=>11/11 2=>22/22 3=>333 4=>444 6=>666 8=>888 9=>999 10=>000

	for (auto& e : um1)
	{
		cout << "um1=>" << " ";
		cout << e.first << "=>" << e.second << " ";
	}
	cout << endl; // um1=> 11=>123 22=>345

	// 清空
	um1.clear();
	for (auto& e : um1)
	{
		cout << e.first << "=>" << e.second << " ";
	}
	cout << endl;
}

重点讲一下[]:
1、若当前容器中已经存在着键值为key的键值对,则返回该键值对value的引用。
2、若当前容器中没有键值为key的键值对,则先插入键值对<key, value()>,然后再返回该键值对中value的引用。

3.3、unordered_multimap的基本介绍

这个容器与unordered_map基本一致,这两个的区别在于multimap允许键值对的冗余,也就是可以允许key和value有不同的值。
存在以下几个区别:find、count、operator[]接口功能
(1)find:

unordered_map 返回键值为key的键值对的迭代器
unordered_multimap 返回底层哈希表中第一个找到的键值为key的键值对的迭代器

(2)count:

unordered_map 键值为key的键值对存在则返回1,不存在则返回0(find成员函数可替代)
unordered_multimap 返回键值为key的键值对的个数(find成员函数不可替代)

(3)operator[]:

我们在unordered_multimap中是没有这个operator[]重载的,因为这个容器中是可以冗余的,所以我们不确定找的是哪一个,会导致很多的错误,所以我们的unordered_multimap是没有operator[]这个的!

void test_unordered_map3()
{
	unordered_multimap<int, string> ummp1;
	ummp1.insert(make_pair(2023, "yes"));
	ummp1.insert(make_pair(2023, "no"));
	ummp1.insert(make_pair(2023, "before"));
	ummp1.insert(make_pair(2023, "now"));
	for (auto& e : ummp1)
	{
		cout << e.first << "=>" << e.second << " ";
	}
	cout << endl;
}

4、map/set 和 unordered_map/unordered_set的区别

在这里插入图片描述

4.1、性能测试

#include <iostream>
#include <set>
#include <unordered_set>
#include <time.h>
using namespace std;

int main()
{
	int N = 1000;
	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;
}

在这里插入图片描述

4.2、总结

(1)、map 和 set:

  1. 优点

有序性:由于红黑树的特性,map和set中的元素都是有序的,这在很多应用中都会简化操作。
检索效率:红黑树的检索效率较高,通常可以达到O(log N)的时间复杂度。

  1. 缺点

空间占用:由于红黑树需要维护树的平衡和节点的额外信息(如父节点、子节点、颜色等),因此相对于其他数据结构(如哈希表),map和set的空间占用率较高。

(2)、unordered_map 和 unordered_set

  1. 优点

查找速度:由于哈希表的特性,unordered_map和unordered_set的查找速度非常快,平均时间复杂度可以达到O(1)。
动态增长:unordered_map和unordered_set可以动态增长,并且可以通过size属性获取元素个数。

  1. 缺点

遍历顺序:由于哈希表的特性,unordered_map和unordered_set中的元素是无序的。因此,遍历它们的顺序可能与插入顺序不同。
哈希表建立:虽然哈希表的查找速度很快,但是哈希表的建立过程(即哈希函数的计算和哈希表的初始化)可能会比较耗时。

(3)、性能总结

1.查找速度:对于查找操作,unordered_map/unordered_set 通常比 map/set 快,因为哈希表的查找时间复杂度通常为O(1),而红黑树的查找时间复杂度为O(log N)。
2.有序性:如果需要保持元素的顺序,map/set 是更好的选择,因为它们是基于红黑树实现的,可以提供有序的键值对或元素存储。
3.空间占用:由于红黑树需要维护额外的信息,因此 map/set 的空间占用率通常高于 unordered_map/unordered_set。
4.遍历顺序:如果遍历顺序很重要,且需要按照插入顺序遍历,那么应该选择 Set 或 Map,因为 unordered_map/unordered_set 的遍历顺序可能与插入顺序不同。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1885150.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

docker部署前端,配置域名和ssl

之前使用80端口部署前端项目后&#xff0c;可以使用IP端口号在公网访问到部署的项目。 进行ICP域名备案后&#xff0c;可以通过域名解析将IP套壳&#xff0c;访问域名直接访问到部署的项目~ 如果使用http协议可以很容易实现这个需求&#xff0c;对nginx.conf文件进行修改&#…

python中的包和模块

目录 一、包与模块 二、第三方包的安装 2.1 pip install 2.2使用 curl 管道 2.3其他安装方法 三、导入单元的构成 3.1pip的使用 四、模块的缓存 一、包与模块 Python 中除了函数库以外&#xff0c;还有非常多且优秀的第三方库、包、模块。 模块Module&#xff1a;以…

Python容器 之 字符串--定义

目录 1.字符串如何定义&#xff1f; 2.定义字符串时遇到特殊内容怎么处理&#xff1f; 1)字符串本身包含引号&#xff0c;如&#xff1a;定义字符串 Im 小明、他叫“小明”。 &#xff08;1&#xff09;如果字符串本身包含单引号,定义的时候不能使用 单引号。 &#xff08…

币界网讯,美国选择 Coinbase 进行数字货币托管

2024 年0 7 月 02 日。华盛顿特区美国法警局 (USMS) 宣布选择 Coinbase Prime 作为其“一级”数字资产的托管人和高级交易服务提供商&#xff0c;USMS这一具有里程碑意义的决定&#xff0c;反映了数字资产日益融入美国联邦司法程序的趋势。Coinbase 是世界排名前十的数字货币交…

解决微信能登陆,但打不开浏览器的WIFI连接问题

双击打开ipv4&#xff0c;改成自动获取 点击确定保存好 断网重新联网

头条系统-05-延迟队列精准发布文章-概述添加任务(db和redis实现延迟任务)、取消拉取任务定时刷新(redis管道、分布式锁setNx)...

文章目录 延迟任务精准发布文章 1)文章定时发布2)延迟任务概述 2.1)什么是延迟任务2.2)技术对比 2.2.1)DelayQueue2.2.2)RabbitMQ实现延迟任务2.2.3)redis实现 3)redis实现延迟任务4)延迟任务服务实现 4.1)搭建heima-leadnews-schedule模块4.2)数据库准备4.3)安装redis4.4)项目…

第十一章 路由器单臂路由配置

实验目标 掌握单臂路由器配置方法&#xff1b; 通过单臂路由器实现不同 VLAN 之间互相通信&#xff1b; 实验背景 某企业有两个主要部门&#xff0c; 技术部和销售部&#xff0c; 分处于不同的办公室&#xff0c; 为了安全和便于管理对两个部门的主机进行了 VLAN 的划分&#x…

ACM美国计算机协会简介及个人下载ACM文献途径

ACM美国计算机协会简介&#xff1a; ACM&#xff08;Association for Computing Machinery&#xff09; 创立于1947年&#xff0c; 是全球历史最悠久和最大的计算机教育、科研机构。ACM目前提供的服务遍及全球100多个国家&#xff0c;会员数超过9万名&#xff0c;涵盖工商业&a…

【力扣】赎金信

&#x1f525;博客主页&#xff1a; 我要成为C领域大神&#x1f3a5;系列专栏&#xff1a;【C核心编程】 【计算机网络】 【Linux编程】 【操作系统】 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 本博客致力于知识分享&#xff0c;与更多的人进行学习交流 ​ 给你两个字符串…

商家转账到零钱开通教程及辅助开通手段详解

商家转账到零钱申请可以通过商家自行申请或者辅助开通手段代申请两种方式&#xff0c;如果自行申请难以过审&#xff0c;辅助开通代申请则成为降低商家时间成本和商户号风险的必要手段&#xff0c;以下从自助申请和辅助开通两种方式说说商家转账到零钱高效开通办法。 自行申请方…

产品经理-对产品经理的认识(1)

今天跟大家聊一下产品经理这个岗位的,产品经理是互联网岗位当中比较火的一个岗位,也是最接近CEO的岗位 产品经理岗位&#xff0c;技术门槛低&#xff0c;薪水和前景都很不错&#xff0c;又处于团队的核心位置 产品经理岗位没有完全相关的专业设置和清晰的学习路径&#xff0c;绝…

【C语言】文件的顺序读写

©作者:末央&#xff06; ©系列:C语言初阶(适合小白入门) ©说明:以凡人之笔墨&#xff0c;书写未来之大梦 目录 前言字符输入输出函数 - fgetc和fputc文本行输入输出函数 - fgets和fputs格式化输入输出函数 - fscanf和fprintf 前言 对文件数据的读写可以分为顺序…

10.优化算法之字符串

1.最长公共前缀 14. 最长公共前缀 - 力扣&#xff08;LeetCode&#xff09; class Solution {public static String longestCommonPrefix(String[] strs) {if(strsnull||strs.length0){return "";}int lengthstrs.length;for(int i0;i<strs[0].length();i){//第一…

FMEA培训如何助你成为工作领域的“稳中求胜”高手

在竞争激烈的职场环境中&#xff0c;每个人都渴望掌握一门能够让自己脱颖而出的技能。而FMEA&#xff08;失效模式与影响分析&#xff09;正是这样一门强大的工具&#xff0c;它不仅能够帮助我们识别并预防潜在的问题&#xff0c;还能提升工作效率&#xff0c;确保项目的顺利进…

从BeanFactory源码看Bean的生命周期

下图是我搜索“Spring Bean生命周期”找到的图片&#xff0c;来自文章——Spring Bean的生命周期 [](https://img2022.cnblogs.com/blog/1942408/202207/1942408-20220713150530777-1198523052.png) 下面&#xff0c;我们从AbstractAutowireCapableBeanFactory的源码中来分析…

无偏归一化自适应心电ECG信号降噪方法(MATLAB)

心电信号作为一种生物信号&#xff0c;含有大量的临床应用价值的信息&#xff0c;在现代生命医学研究中占有重要的地位。但心电信号低频、低幅值的特点&#xff0c;使其在采集和传输的过程中经常受到噪声的干扰&#xff0c;使心电波形严重失真&#xff0c;从而影响后续的病情分…

无人机企业需要什么资质?

无人机企业所需的资质主要可以分为几大类&#xff0c;以确保其合法、安全、高效地进行相关业务活动。以下是对这些资质的详细解释和归纳&#xff1a; 1. 基础企业资质&#xff1a; - 工商营业执照&#xff1a;这是企业合法经营的基本证书&#xff0c;所有企业都需要取得。无人…

查询 条件列值用notepad++批量添加单引号和逗号

参考&#xff1a;Notepad批量添加引号_notepad字符串统一加引号-CSDN博客 我需要批量修改数据表中某一列值指定的部分列&#xff0c;比如某个编号为CP0408242321001到CP0408242321101的条件。 我从数据表中把这个条件的所有编号复制出来了粘贴到了notepad里面。 如下图所示 从…

Softmax作为分类任务中神经网络输出层的优劣分析

Softmax作为分类任务中神经网络输出层的优劣分析 在深度学习领域&#xff0c;Softmax函数作为分类任务中神经网络的输出层&#xff0c;被广泛应用并展现出强大的优势。然而&#xff0c;任何技术都有其两面性&#xff0c;Softmax函数也不例外。本文将从多个角度深入分析Softmax…

大型语言模型的长期记忆能力--HippoRAG

在人工智能领域&#xff0c;特别是自然语言处理&#xff08;NLP&#xff09;中&#xff0c;大型语言模型&#xff08;LLMs&#xff09;的长期记忆能力一直是研究的热点和难点。人类大脑能够在不断变化的环境中存储和更新大量知识&#xff0c;而现有的LLMs在预训练后整合新经验时…