【C++】unordered_set 容器的最全解析(什么是unordered_set?unordered_set的常用接口有那些?)

news2024/11/14 15:59:27

目录

一、前言

 二、预备知识 

💢关联式容器💢 

💢键值对💢  

💢哈希结构的关联式容器💢  

三、unordered_set 详解  

🔥unordered_set 的介绍 

🔥unordered_set 的构造 

🔥unordered_set 的使用 

🥝 insert

🍇find 

🍍 erase

🍉size 

🍋empty 

🍓swap 

🍌count

🍈迭代器 

🔥set 和 unordered_set 的区别 

🍐性能测试 

四、常考面试题 

五、共勉


一、前言

【unordered_set】  是 STL 中的容器之一,不同于普通容器,它的查找速度极快,常用来存储各种经常被检索的数据,因为容器的底层是【哈希表。除此之外,还可以借助其特殊的性质,解决部分难题。

 二、预备知识 

 在正式学习 unordered_set 之前,首先要有一些预备知识,否则后面可能看不懂相关操作 

💢关联式容器💢 

在以往的 【STL 容器学习中,我们接触到的都是 序列式容器,比如 stringvectorlistdeque 等,序列式容器的特点就是 底层为线性序列的数据结构,就比如 list,其中的节点是 线性存储 的,一个节点存储一个元素,其中存储的元素都可序,但未必有序  

  • 关联式容器 则比较特殊,其中存储的是 <key, value> 的 键值对,这就意味着可以按照 键值大小 key 以某种特定的规则放置于适当的位置,关联式容器 没有首尾的概念,因此没有头插尾插等相关操作,本文中学习的 unordered_set 就属于 关联式容器 

注意: stackqueue 等适配器也属于序列式容器,因为他们的底层是 deque 等容器 


💢键值对💢  

键值对】是 一种用来表示具有一一对应关系的结构,该结构中一般只包含两个成员变量:key 和 value,前者表示 键值,后者表示 实值 关联式容器的实现离不开【键值对

因此在标准库中,专门提供了这种结构  pair 定义如下 : 

//SGI 版 STL 中的实现
template <class T1, class T2>
struct pair {
  typedef T1 first_type;
  typedef T2 second_type;
 
  T1 first;	
  T2 second;	
  pair() : first(T1()), second(T2()) {}
  pair(const T1& a, const T2& b) : first(a), second(b) {}
 
#ifdef __STL_MEMBER_TEMPLATES
  template <class U1, class U2>
  pair(const pair<U1, U2>& p) : first(p.first), second(p.second) {}
#endif
};
  • pair 中的 first 表示 键值second 则表示 实值,在给 【关联式容器】 中插入数据时,可以构建 pair 对象 

 比如下面就构建了一个 键值 key 为 string实值 value 为 int 的匿名 键值对 pair 对象 

pair<string, int>("hehe", 123);
  • 可以将此匿名对象传入 关联式容器 中,当然这样写未免过于麻烦了,于是库中设计了一个函数模板 make_pair,可以根据传入的参数,去调用 pair 构建对象并返回
make_pair("hehe", 123);	//构建出的匿名对象与上面的一致

make_pair 的定义如下所示:  

template <class T1,class T2>
pair<T1,T2> make_pair (T1 x, T2 y)
{
  return ( pair<T1,T2>(x,y) );
}
  • 该函数实际会被编译器优化为 内联函数,因此不会造成过多消耗,可以放心使用 

💢哈希结构的关联式容器💢  

所以在 C++ 标准中,共提供了四种 哈希结构的关联式容器  

  • unordered_set
  • unordered_multiset
  • unordered_map
  • unordered_multimap

关于 树形结构的关联式容器 将在 二叉搜索树 中学习

树型结构与哈希结构的关联式容器功能都是一模一样的,不过 哈希结构查找比树型结构快得多 -> O(1)

注:

  • STL 中选择的树型结构为 红黑树 RB-Tree
  • 树型结构中的元素 中序遍历 后有序,而哈希结构中的元素无序

三、unordered_set 详解  

在C++98中,STL提供了底层为 红黑树结构 的一系列关联容器,在查询时效率可以达到 log(N)。但是在较差的情况下 ,需要比较红黑树的高度次,当树中节点非常多的时候,查询效率也会不理想达到 log(N)

  • 最好的查询是,进行很少的比较次数就能够将元素找到,因此在C++11中,STL又提供了 4 个 unordered系列的关联式容器。 

  •  接下来我们将会对,unordered_set 进行详细的介绍,其余的容器将会在后续的文章中讲述。

🔥unordered_set 的介绍 

unordered_ set存储 没有特定顺序的唯一元素的容器,允许基于它们的值快速检索单个元素。

  • unordered_ set 中,元素的值与唯一标识它的键同时存在。 键是不可变的,因此,unordered_ set 中的元素在容器中不能被修改,但是它们可以被插入和删除
  • 在内部,unordered_ set 中的元素不按任何特定顺序排序,而是根据它们的哈希值组织到桶中,以允许直接根据它们的值快速访问单个元素(平均平均时间复杂度恒定)。 
  • unordered_ set 容器在通过键访问单个元素时比 set容器更快,尽管它们在通过元素子集进行范围迭代时通常效率较低。 
  • 容器中的迭代器至少是前向迭代器。 


🔥unordered_set 的构造 

构造一个 unordered_ set 容器对象,根据使用的构造函数版本初始化其内容,我们主要掌握3种方式即可:

(1)构造一个某个类型的容器 

unordered_set<int> s1; // 构造int类型的空容器

(2)拷贝构造某个类型的容器 

unordered_set<int> us2(us1); // 拷贝构造同类型容器us1的复制品

(3)使用迭代器区间进行初始化构造 

  •  构造一个 unordered_set 对象,其中包含 【first ,last )中的每一个元素副本。
string str("helloworld");
unordered_set<char> us3(str.begin(), str.end()); // 构造string对象某段区间的复制品

🔥unordered_set 的使用 

 unordered_set 的成员函数主要分为:迭代器,容量操作,修改操作。

  • 需要注意的是,对于unordered_ set 而言,它存储的数据是无序的,并组它是一个单向迭代器。

  •  我这里举几个用的例子,帮助大家理解

🥝 insert

unordered_ set 中插入新元素。

  • 每个元素只有在它不等同于容器中已经存在的任何其他元素时才会被插入,也就是说unordered_ set 中的每个元素是唯一的。 

代码示例: 

void test_unordered()
{
	unordered_set<int> us1;

	// 插入元素
	us1.insert(4);
	us1.insert(5);
	us1.insert(2);
	us1.insert(2);
	us1.insert(1);
	us1.insert(3);
	us1.insert(3);

	// 遍历
	for (auto e : us1)
	{
		cout << e << " ";
	}
}
  •  可以看到当插入重复元素时,重复元素 是去掉了的,并且没有进行排序。


🍇find 

在容器中搜索值为 k 的元素,如果找到它,则返回一个迭代器,否则返回unordered_ set::end (容器末端之 前的元素)的迭代器。 

代码示例: 

void test_unordered()
{
	unordered_set<int> us;

	// 插入元素
	us.insert(4);
	us.insert(5);
	us.insert(2);
	us.insert(2);
	us.insert(1);
	us.insert(3);
	us.insert(3);

	unordered_set<int>::iterator pos = us.find(3);
	if (pos != us.end())
	{
		cout << "3存在" << endl;
	}
}


🍍 erase

 从 unordered_ set 容器中移除单个元素或一组元素( [first,last) )。

  • 通过调用每个元素的析构函数,这有效地减少了容器的大小。 

(1)从容器中删除单个元素(搭配find使用) 

void test_unordered()
{
	unordered_set<int> us;

	// 插入元素
	us.insert(4);
	us.insert(5);
	us.insert(2);
	us.insert(2);
	us.insert(1);
	us.insert(3);
	us.insert(3);

	unordered_set<int>::iterator pos = us.find(3);
	if (pos != us.end())
	{
		us.erase(pos); // 删除元素3
		cout << "删除成功" << endl;
	}
	else
	{
		cout << "删除失败" << endl;
	}

	// 遍历
	for (auto e : us)
	{
		cout << e << " ";
	}
}
  • 可以看到 3 已经被删除了 

(2)从容器中删除单个元素(直接传要删除的元素) 

void test_unordered()
{
	unordered_set<int> us;

	// 插入元素
	us.insert(4);
	us.insert(5);
	us.insert(2);
	us.insert(2);
	us.insert(1);
	us.insert(3);
	us.insert(3);

	us.erase(5); // 删除元素5

	// 遍历
	for (auto e : us)
	{
		cout << e << " ";
	}
}
  •  可以看到 5 已经被删除了

那么它和第 1 种的区别是什么呢?

  • erase(x) :如果 x 存在就删除;如果不存在,不做任何改变
  • erase(pos):如果 x 存在就删除;如果不存在,此时 pos 位置指向 set::end 的迭代器,那么程序就会报错。

其实这种方法本质上可以理解为 erase 去调用了 迭代器find


🍉size 

返回 unordered_set 容器中的元素数量 

代码示例: 

void test_unordered()
{
	unordered_set<string> us;

	// 构造元素
	us = { "milk", "potatoes", "eggs" };
	cout << "size: " << us.size() << endl;

	// 插入元素
	us.insert("pineapple");
	cout << "size: " << us.size() << endl;

	// 插入重复元素
	us.insert("milk");
	cout << "size: " << us.size() << endl;
}


🍋empty 

 返回一个bool值,指示unordered_ set 容器是否为空,即其大小是否为0。

  • 这个函数不会以任何方式修改数组的内容。

 代码示例:

void test_unordered()
{
	// us1构造3个元素
	unordered_set<string> us1 = { "milk", "potatoes", "eggs" };

	// us2构造一个空容器
	unordered_set<string> us2;

	cout << "us1 " << (us1.empty() ? "is empty" : "is not empty") << endl;
	cout << "us2 " << (us2.empty() ? "is empty" : "is not empty") << endl;
}


🍓swap 

通过 ust 的内容交换容器的内容, ust 另一个包含相同类型元素的 unordered_ set 对象。大小可能不同。

  • 这个函数在容器之间交换指向数据的内部指针,而不实际对单个元素执行任何复制或移动,允许常量时间执行,无论大小如何。 

代码示例:

void test_unordered()
{
	unordered_set<string> us1 = { "iron","copper","oil" };
	unordered_set<string> us2 = { "wood","corn","milk" };

	// 交换容器的内容
	us1.swap(us2);

	// 遍历us1
	for (const string& x1 : us1)
	{
		cout << x1 << " ";
	}
	cout << endl;

	// 遍历us2
	for (const string& x2 : us2)
	{
		cout << x2 << " ";
	}
}


🍌count

在容器中搜索值为 k 的元素,并返回找到的元素数。

  • 因为 unordered_ set 容器不允许重复值,这意味着如果容器中存在具有该值的元素,则函数实际返回1,否则返回 0。

代码示例:

void test_unordered()
{
	unordered_set<string> us = { "hat", "umbrella", "suit" };

	// 容器中值为"hat"的元素个数
	cout << us.count("hat") << endl;

	// 容器中值为"red"的元素个数
	cout << us.count("red") << endl;
}


🍈迭代器 

 unordered_set 当中迭代器相关函数如下:

  注意: set 是双向迭代器,而unordered_ set 是单向迭代器


🔥set 和 unordered_set 的区别 

setunordered_set 是 C++ 标准模板库(STL)中的两种关联容器,它们都有存储唯一元素的特性,但它们在底层实现、元素存储顺序、查找和插入的性能上存在显著的区别。 

  • set使用自平衡二叉搜索树实现,元素是有序的,适合需要有序存储的场景,操作的时间复杂度是 O(log⁡n)。 
  • unordered_set使用哈希表实现,元素是无序的,适合只关心元素存在性而不关心顺序的场景,操作的时间复杂度在理想情况下是 O(1)。 

🍐性能测试 

 下面是性能测试代码,包含 大量重复、部分重复、完全有序 三组测试用例,分别从 插入、查找、删除 三个维度进行对比

  • 注:测试性能用的是 Release 版,这里的基础数据量为 100 w 
#include <iostream>
#include <vector>
#include <set>
#include <unordered_set>

using namespace std;

int main()
{
	const size_t N = 1000000;

	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 << s.size() << endl;
	cout << 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;
	
	return 0;
}

插入大量重复数据 

插入数据 大量重复 ---- 结果:

  • 插入:哈希 比 红黑 快 88%
  • 查找:哈希 比 红黑 快 100%
  • 删除:哈希 比 红黑 快 37%

总的来说,在数据 随机 的情况下,哈希各方面都比红黑强,在数据 有序 的情况下,红黑更胜一筹

单就 查找 这一个方面来说:哈希 一骑绝尘,远远的将红黑甩在了身后


四、常考面试题 

题目:最长连续序列
链接:128. 最长连续序列 - 力扣(LeetCode)

题目分析: 

  • 对于数组中存在的连续序列,为了统计每个连续序列的长度,我们希望直接定位到每个连续序列的起点,从起点开始遍历每个连续序列,从而获得长度。 

那么如何获取到每个连续序列的起点呢,或者说什么样的数才是一个连续序列的起点?

  • 答案是这个数的前一个数不存在于数组中,因为我们需要能够快速判断当前数num的前一个数num - 1是否存在于数组中。

同时当我们定位到起点后,我们就要遍历这个连续序列,什么时候是终点呢?

  • 答案是当前数num的后一个数nunm + 1不存在于数组中,因此我们需要能够快速判断当前数num的后一个数num + 1是否存在于数组中。

为了实现上述需求,我们使用哈希表来记录数组中的所有数以实现对数值的快速查找

class Solution {
public:
    int longestConsecutive(vector<int>& nums) 
    {
        // 记录最长连续序列的长度
        int res = 0;
        unordered_set<int> num_set(nums.begin(),nums.end());
        int seqlen;
        for(auto ch : num_set)
        {
             // 如果当前的数是一个连续序列的起点,统计这个连续序列的长度
             // 如果 ch - 1 不在 num_set 中,则返回 true;如果 ch - 1 在 num_set 中,则返回 false。
             if(!num_set.count(ch-1))
             {
                seqlen = 1; // 连续序列的长度,初始为1
                while(num_set.count(++ch))
                {
                    seqlen++; // 不断查找连续序列,直到num的下一个数不存在于数组中
                }
                res = max(res,seqlen);
             }
        }
        return res;
    } 
};


五、共勉

以下就是我对 【C++】unordered_set 容器 的理解,如果有不懂和发现问题的小伙伴,请在评论区说出来哦,同时我还会继续更新【C++】请持续关注我哦!!! 

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

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

相关文章

解除 Excel 表格的文档保护全攻略

在日常工作和学习中&#xff0c;我们可能会遇到 Excel 表格被保护无法编辑的情况。别担心&#xff0c;今天就为大家分享几种解除 Excel 表格文档保护的方法。 一、导入腾讯文档 可以将受保护的 Excel 表格上传到腾讯文档。在部分情况下&#xff0c;腾讯文档会尝试自动解除表…

零基础国产GD32单片机编程入门(九)低功耗模式实战含源码

文章目录 一.概要二.GD32单片机低功耗基本介绍三.GD32单片机待机模式介绍四.待机低功耗例程实验五.工程源代码下载六.小结 一.概要 在生活中通过关掉用电器可以实现省电节能的目的&#xff0c;同样的道理单片机也可以通过这种方法实现降低功耗。单片机是由许多部件组成&#x…

ruoyi-vue-plus服务端打包报错的问题

对idea不熟&#xff0c;不知道在哪里输入打包命令&#xff0c;只会用手点击进行打包&#xff0c;然后就报错了 官方文档给的打包命令是 mvn clean package -D maven.test.skiptrue -P prod 从命令中可以看到跳过了maven测试&#xff0c;那么就要设置idea打包时跳过测试&…

k8s单master多node环境搭建-k8s版本低于1.24,容器运行时为docker

k8s 1.20.6单master多node环境搭建 1.环境规划2.初始化服务器1&#xff09;配置主机名2&#xff09;设置IP为静态IP3&#xff09;关闭selinux4&#xff09;配置主机hosts文件5&#xff09;配置三台主机之间免密登录6&#xff09;关闭交换分区swap&#xff0c;提升性能7&#xf…

【Python基础】字符串类型

本文收录于 《Python编程入门》专栏&#xff0c;从零基础开始&#xff0c;分享一些Python编程基础知识&#xff0c;欢迎关注&#xff0c;谢谢&#xff01; 文章目录 一、前言二、Python 字符串类型2.1 Python访问字符串中的值2.2 Python 转义字符2.3 Python 字符串运算符2.4 Py…

Bluetooth: gatt profile

Gatt 主要是描述了attribute的排列方式&#xff1b; Attribute caching 这个机制允许client只搜索一次server即可&#xff0c;当重连后不需要再搜索直接使用之前的。如果server的服务发生了变化&#xff0c;需要通过 service change indication 告诉client&#xff1b; client…

网优学习干货:2.6G仿真操作(2)

导入仿真区域图层 建立仿真站点组 设置仿真任务-结果图层和楼宇仿真高度 仿真结果统计-结果图层渲染 仿真结果统计-结果导出 目录 导入天线文件-导入方法与覆盖仿真相同&#xff0c;但天线文件需要包含PDSCH波束文件 将Beamforming天线添加到基站 如果在步骤④中没有找到Beamfo…

Web自动化测试实战--博客系统

&#x1f3a5; 个人主页&#xff1a;Dikz12&#x1f525;个人专栏&#xff1a;测试&#x1f4d5;格言&#xff1a;吾愚多不敏&#xff0c;而愿加学欢迎大家&#x1f44d;点赞✍评论⭐收藏 目录 1.项目效果展示 2.编写web测试用例 3.自动化测试脚本开发 3.1创建空项目 引…

构建大师:深入理解Linux下的Make和Makefile

引言 在软件开发的世界里&#xff0c;构建过程是一项繁琐而重要的任务。无论是简单的脚本还是复杂的软件项目&#xff0c;都需要一种方式来自动化编译、链接以及测试等过程。在Linux环境下&#xff0c;Make工具和它的配置文件——Makefile&#xff0c;成为了许多开发者构建项目…

计算机硬件的组成

目录 前言 计算机系统组成 计算机硬件的组成 1、控制器 2、运算器 3、主存储器 4、辅助存储器 5、输入设备 6、输出设备 最后 前言 计算机已成为不可或缺的工具。无论是个人电脑还是服务器集群&#xff0c;其背后都是由一系列硬件组件协同工作的结果。 本文讲介绍计…

ssrf攻击本地fastcgi漏洞复现

目录 环境&#xff1a;UbuntuNginxphp 代码 开始测试 查看 环境搭建 环境&#xff1a;UbuntuNginxphp 代码 <?php highlight_file(__FILE__); $url $_GET[url]; $curl curl_init($url);curl_setopt($ch,CURLOPT_URL,$url); curl_setopt($curl, CURLOPT_HEADER, 0…

滚雪球学MyBatis-Plus(02):环境准备

环境准备 本地开发环境参考如下&#xff1a; 开发工具&#xff1a;IntelliJ IDEA 2021.3.2JDK版本&#xff1a; JDK 1.8Spring Boot版本&#xff1a;2.3.1.RELEASEMaven版本&#xff1a;Apache Maven 3.8.2MySQL&#xff1a;5.6 前言 在上期内容中&#xff0c;我们系统地介绍了…

【多线程】设计模式之单例模式

&#x1f490;个人主页&#xff1a;初晴~ &#x1f4da;相关专栏&#xff1a;多线程 / javaEE初阶 一、什么是设计模式 设计模式好⽐象棋中的 "棋谱". 红⽅当头炮, ⿊⽅⻢来跳. 针对红⽅的⼀些⾛法, ⿊⽅应招的时候有⼀些固定的套路. 按照套路来⾛局势就不会吃亏. …

【微服务】接口的幂等性怎么设计?

一、什么是幂等&#xff1f; 幂等性&#xff1a;短时间内&#xff0c;对于相同输入的请求&#xff0c;无论进行多少次重复操作&#xff0c;都应该和单次调用的结果一致。 二、幂等问题产生的原因是什么&#xff1f;(或者说为什么需要实现幂等性?) 1、前端重复提交 在用户注…

高频Postman接口测试面试题

一、Postman在工作中使用流程是什么样的&#xff1f; 新建集合管理根据接口所属的模块&#xff0c;在集合中不同模块下编写接口测试用例处理接口之间的数据关联操作添加环境变量在tests tab下中增加断言调试接口&#xff0c;确保接口能被正常调用批量运行用例或者导出通过Newm…

STM32H750VBT6烧录源码无反应的问题

当烧录后出现这种情况下&#xff0c;点击魔术棒里面 Linker,勾选第一个方框后再次烧录即可。

【机器学习】聚类算法的基本概念和实例代码以及局部度量学习的概念和实例代码

引言 聚类算法在许多领域都有广泛的应用&#xff0c;例如数据挖掘、生物信息学、图像处理等。 文章目录 引言一、聚类算法1.1 K-Means算法1.2 DBSCAN算法1.3 层次聚类&#xff08;Hierarchical Clustering&#xff09;算法1.4 高斯混合模型&#xff08;Gaussian Mixture Model&…

Python系统教程02

Python 中基本运算符的使用变量基本运算符和变量编写简单的 Python 程序 一、Python 中的加法、减法、乘法、除法、 1.1 Python 中的""运算符 "" 可以用来计算两个数的和 "" 可以用来拼接 运算符可以用来计算两个数的和运算符可以连接多个字符…

Leetcode面试经典150题-36-有效数独升级版-37.解数独

解法都在代码里&#xff0c;不懂就留言或者私信&#xff0c;比第一题稍微难点 public static void solveSudoku(char[][] board) {/**定义三个二维数组分别代表行、列、桶&#xff08;每9个格子&#xff09;*/boolean[][] rowExists new boolean[9][10];boolean[][] colExist…

不用U盘重装win10/11

创建适用于 Windows 的安装介质 Windows 10 Windows 8.1 Windows 7 Microsoft 365 免费试用版正在等待你使用 立即解锁 你可以使用安装介质&#xff08;U 盘或 DVD&#xff09;来安装 Windows 的新副本、执行全新安装或重新安装 Windows。 要创建安装介质&#xff0c;请转到…