C++ unordered_set和unordered_map

news2024/11/17 1:45:56

哈希

  • 1. unordered_set/unordered_map
    • 1.1 背景
    • 1.2 unordered_set
      • 1.2.1 特性
      • 1.2.2 常用方法
    • 1.3 unordered_map
      • 1.3.1 特性
      • 1.3.2 常用方法
  • 2. 哈希
    • 2.1概念
    • 2.2 哈希冲突
      • 2.2.1哈希函数
      • 2.2.2 解决哈希冲突
        • 2.2.2.1 闭散列
        • 2.2.2.2 开散列

1. unordered_set/unordered_map

1.1 背景

之前我们不是学习了以红黑树为底层的容器set和map吗,那是C++98提供的关联式容器,而我们今天要学习的unordered_set/unordered_map是在C++11引入的新容器,看名字就知道和set、map的关系匪浅。set、map的查询效率可以达到log2(N),但是在数据很多的时候,效率也不是很理想。所以就引入了这两个容器。那这两个容器能把效率提高多少呢,看完这篇文章就知道了。

1.2 unordered_set

1.2.1 特性

  1. unordered_set(无序集合)是一种不按特定顺序的存储唯一元素,根据关键字可以快速查询到元素的容器。
  2. unordered_set中的存放的元素不能修改,插入和删除是可以的。
  3. 和set相比查询效率高,为常数级,但它通常在遍历元素子集的范围迭代方面效率较低。
  4. 该容器的一些特性和set差不多,可以放在一起比较记忆。

1.2.2 常用方法

unorder_set<string> first容器定义
first.empty()判断容器是否是空,是空返回true,反之为false
first.size()返回容器大小
first.maxsize()返回容器最大尺寸
first.begin()返回迭代器开始
first.end()返回迭代器结束
first.find(value)返回value在迭代器的位置
first.count(key)返回key在容器的个数
first.insert(value)将value插入到容器中
first.erase(key)通过key删除
first.clear()清空容器
在线文档

1.3 unordered_map

1.3.1 特性

  1. unordered_map是一个将key和value关联起来的容器,它可以高效的根据单个key值查找对应的value。
  2. key值应该是唯一的,key和value的数据类型可以不相同。
  3. unordered_map存储元素时是没有顺序的,只是根据key的哈希值,将元素存在指定位置,所以根据key查找单个value时非常高效,平均可以在常数时间内完成。
  4. unordered_map查询单个key的时候效率比map高,但是要查询某一范围内的key值时比map效率低。
  5. 可以使用[]操作符来访问key值对应的value值。

1.3.2 常用方法

insert():插入数据。
erase():删除数据。
find():查找数据。
clear():数据的清空。
empty():数据的判空。
size():获取有效元素的大小。
count():获取键值中查找元素的个数。如果有返回1,否则返回0。
rbegin():在反向迭代器中表示起始元素。
rend():在反向迭代器中表示末尾元素。
operator[key]:通过键值(key)获取该key对应的value。

2. 哈希

2.1概念

在前面学习过的数据结构中,存放的关键值和存储的位置没有任何关联,所以查找一个元素必须要经过多次的比较才能得到该元素。那么有没有一种方法可以实现元素之间不用比较也能查找到该元素呢?哈希的思想就引入进来了。

插入操作
       在往该结构中插入元素时,通过关键码和一个函数,计算出存放该元素的位置。

查找操作
       怎么放进去的就怎么找出来,对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功

几个名词:

  1. 哈希函数(散列函数)(将关键码转换成位置的方法)
  2. 哈希表(散列表)(使用上述方法构造出来的结构)

一个例子:
数据{1,9,16,10,8,0}
哈希函数 hash(key) = key % 10;
在这里插入图片描述
相信大家看了这个例子,可以体会到哈希的妙,但这出现了一个问题,两个元素映射到了同一个地方,这种情况叫哈希冲突,解决该问题也是我们研究的一个重要问题。

2.2 哈希冲突

不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞

2.2.1哈希函数

产生这种情况的原因可能是哈希函数设计的不够合理。

设计原则:

  1. 形式简单
  2. 计算出来的结果分布均匀
  3. 哈希函数的定义域必须包括需要存储的全部关键码

一些常见哈希函数

  1. 直接定址法–(常用)
    取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B 优点:简单、均匀 缺点:需要事先知道关键字的分布情况 使用场景:适合查找比较小且连续的情况
  2. 除留余数法–(常用)
    设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函数:Hash(key) = key%
    p(p<=m),将关键码转换成哈希地址
  3. 平方取中法–(了解)
    假设关键字为1234,对它平方就是1522756,抽取中间的3位227作为哈希地址; 再比如关键字为4321,对它平方就是18671041,抽取中间的3位671(或710)作为哈希地址
    平方取中法比较适合:不知道关键字的分布,而位数又不是很大的情况
  4. 折叠法–(了解)
    折叠法是将关键字从左到右分割成位数相等的几部分(最后一部分位数可以短些),然后将这 几部分叠加求和,并按散列表表长,取后几位作为散列地址。 折叠法适合事先不需要知道关键字的分布,适合关键字位数比较多的情况
  5. 随机数法–(了解)
    选择一个随机函数,取关键字的随机函数值为它的哈希地址,即H(key) = random(key),其中 random为随机数函数。 通常应用于关键字长度不等时采用此法
  6. 数学分析法–(了解)
    设有n个d位数,每一位可能有r种不同的符号,这r种不同的符号在各位上出现的频率不一定相同,可能在某些位上分布比较均匀,每种符号出现的机会均等,在某些位上分布不均匀只有某几种符号经常出现。可根据散列表的大小,选择其中各种符号分布均匀的若干位作为散列地址、

2.2.2 解决哈希冲突

解决该冲突常见的两种方法是开散列和闭散列。

2.2.2.1 闭散列
  1. 线性探测:如果发现插入元素的位置已经有元素了,那就依次往后找空位放进去,比如上述例子的0元素应该放在2位置上。

插入操作:
       通过哈希函数计算出位置,如果该位置为空则放进去,否则依次往后。
查找操作:
       通过哈希函数计算出位置,比对值是否一样,如果一样就查找成功,如果碰到空则证明没有该元素(因为元素是依次往后探测的,不可能留空隙,所以遇到空就证明没有该元素)。
删除操作:
       不能随便的物理删除一个元素,不然会影响查找操作,比如现在有一个场景:你删除了一个元素,然后要查找的元素在你删除元素的后面,根据上面的查找规则,你是查不到这个元素的。所以我们采取了标志位的方法来删除元素,删除一个元素就是简单的把该元素的标志位改为已删除。

代码实现:

enum State
	{
		EMPTY,
		EXIST,
		DELETE
	};

	template<class K, class V>
	struct HashData
	{
		pair<K, V> _kv;
		State _state = EMPTY; // 标记
	};

//将关键字转化成无符号整型,这样才能进行取余操作
	template<class K>
	struct HashFunc
	{
		size_t operator()(const K& key)
		{
			return (size_t)key;
		}
	};

	// 特化
	template<>
	struct HashFunc<string>
	{
		size_t operator()(const string& s)
		{
			size_t hash = 0;
			for (auto e : s)
			{
				hash += e;
				hash *= 131;
			}

			return hash;
		}
	};


	template<class K, class V, class Hash = HashFunc<K>>
	class HashTable
	{
	public:
		HashTable(size_t size = 10)
		{
			_tables.resize(size);
		}

		HashData<K, V>* Find(const K& key)
		{
			Hash hs;
			// 线性探测
			size_t hashi = hs(key) % _tables.size();
			while (_tables[hashi]._state != EMPTY)
			{
				if (key == _tables[hashi]._kv.first
					&& _tables[hashi]._state == EXIST)
				{
					return &_tables[hashi];
				}

				++hashi;
				hashi %= _tables.size();
			}

			return nullptr;
		}

		bool Insert(const pair<K, V>& kv)
		{
			if (Find(kv.first))
				return false;
			//哈希表应满足元素个数和空间的比率,	
			//如果 ((double)_n / (double)_tables.size() >= 0.7)就该扩容了。
			if (_n * 10 / _tables.size() >= 7)
			{

				HashTable<K, V, Hash> newHT(_tables.size() * 2);
				// 遍历旧表,插入到新表
				for (auto& e : _tables)
				{
					if (e._state == EXIST)
					{
						newHT.Insert(e._kv);
					}
				}
				_tables.swap(newHT._tables);
			}

			Hash hs;
			// 线性探测
			size_t hashi = hs(kv.first) % _tables.size();
			while (_tables[hashi]._state == EXIST)
			{
				++hashi;
				hashi %= _tables.size();
			}

			_tables[hashi]._kv = kv;
			_tables[hashi]._state = EXIST;
			++_n;

			return true;
		}

		bool Erase(const K& key)
		{
			HashData<K, V>* ret = Find(key);
			if (ret)
			{
				_n--;
				ret->_state = DELETE;
				return true;
			}
			else
			{
				return false;
			}
		}

	private:
		vector<HashData<K, V>> _tables;
		size_t _n = 0;  // 实际存储的数据个数

使用这种方式解决冲突虽然很简单,但是如果冲突验证,那么查询的效率会降低很多。

2.2.2.2 开散列

开散列法又叫链地址法(开链法),将冲突的元素通过链表链接起来,将链表的头保存在哈希表中,每个链表也叫桶。
还是上面的例子,用开散列法看看
在这里插入图片描述
这种结构的插入、删除、查找操作就是链表的基本操作,这里我们就不多说了,直接看代码。

//将关键字映射成整型
	template<class K>
	struct HashFunC
	{
		size_t operator()(const K& key)
		{
			return (size_t)key;
		}
	};
	//模版特化,string常作为key
	template<>
	struct HashFunC<string>
	{
		//将字符串每个字母的ASCII乘一个系数然后累加
		size_t operator()(const string& key)
		{
			size_t hash = 0;
			for (char c : key)
			{
				hash += c;
				hash *= 131;
			}

			return hash;
		}
	};
	
	template<class K,class V>
	struct HashBucketNode
	{
		HashBucketNode(const pair<K,V>& value)
			:_value(value)
			,next(nullptr)
		{}
		pair<K, V> _value;
		HashBucketNode<K, V>* next;
	};

	template<class K,class V,class Hash = HashFunC<K>>
	class HashBucket
	{
		typedef struct HashBucketNode<K,V> Node;
	public:
		HashBucket(size_t size = 10)
			:_table(size,nullptr)
			,_size(0)
		{}
		~HashBucket()
		{
			Clear();
		}
		// 哈希桶中的元素不能重复
		Node* Insert(const pair<K, V>& value)
		{
			if (!Find(value.first))
			{
				CheckCapacity();
				size_t hashi = HashFunc(value.first);
				//头插
				Node* newnode = new Node(value);
				newnode->next = _table[hashi];
				_table[hashi] = newnode;
				_size++;
				return newnode;
			}
		}

		// 删除哈希桶中为data的元素(data不会重复)
		bool Erase(const K& key)
		{
			if (Find(key))
			{
				size_t hashi = HashFunc(key);
				Node* pre = nullptr;
				Node* cur = _table[hashi];
				while (cur)
				{
					if (cur->_value.first == key)
					{
						//头删单独处理
						if (!pre)
							_table[hashi] = cur->next;
						else
						pre->next = cur->next;
						delete cur;
						_size--;
						return true;
					}
					pre = cur;
					cur = cur->next;
				}
			}
			return false;
		}

		Node* Find(const K& key)
		{
			size_t hashi = HashFunc(key);
			Node* cur = _table[hashi];
			while (cur)
			{
				if (cur->_value.first == key)
					return cur;
				cur = cur->next;
			}

			return nullptr;
		}
		void Swap(HashBucket<K,V,Hash>& ht)
		{
			_table.swap(ht._table);
			swap(_size, ht._size);
		}
		void Clear()
		{
			for (auto& e : _table)
			{
				if (e)
				{
					Node* cur = e;
					while (cur)
					{
						Node* next = cur->next;
						delete cur;
						cur = next;
					}
					e = nullptr;
				}
			}
		}

		size_t Size()const
		{
			return _size;
		}

		bool Empty()const
		{
			return 0 == _size;
		}
	private:
		size_t HashFunc(const K& key)
		{
			Hash hash;
			return hash(key) % _table.size();
		}
		void CheckCapacity()
		{
			//当装填因子为1
			if (_size == _table.size())
			{
				HashBucket<K, V,Hash> newHashBucket(_table.size() * 2);
				for (int i = 0; i < _table.size(); i++)
				{
					Node* cur = _table[i];
					while(cur)
					{
						Node* next = cur->next;
						newHashBucket.Insert(cur->_value);
						cur = next;
					}
				}
				Swap(newHashBucket);
			}
		}

	private:
		vector<Node*> _table;
		size_t _size;
	};

在这里插入图片描述

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

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

相关文章

新台阶——蓝桥杯单片机省赛第十四届程序设计题目

在做十四届题目之前&#xff0c;常常听学长说&#xff0c;十四届以前拿省一真的是右手就行&#xff0c;并不相信&#xff0c;在经历十四届痛苦的大量修bug和优化之后&#xff0c;或许学长的话真说对了几分。话不多说&#xff0c;我们开始一起完成单片机第十四届程序设计题目。 …

【】(综合练习)博客系统

在之前的学些中&#xff0c;我们掌握了Spring框架和MyBatis的基本使用&#xff0c;接下来 我们就要结合之前我们所学的知识&#xff0c;做出一个项目出来 1.前期准备 当我们接触到一个项目时&#xff0c;我们需要对其作出准备&#xff0c;那么正规的准备是怎么样的呢 1.了解需求…

基于傅里叶描述子的手势动作识别,Matlab实现

博主简介&#xff1a; 专注、专一于Matlab图像处理学习、交流&#xff0c;matlab图像代码代做/项目合作可以联系&#xff08;QQ:3249726188&#xff09; 个人主页&#xff1a;Matlab_ImagePro-CSDN博客 原则&#xff1a;代码均由本人编写完成&#xff0c;非中介&#xff0c;提供…

ubuntu22.04物理机双系统手动分区

ubuntu22.04物理机双系统手动分区 文章目录 ubuntu22.04物理机双系统手动分区1. EFI系统分区2. 交换分区3. /根分区4. /home分区分区后的信息 手动分区顺序&#xff1a;EFI系统分区(/boot/efi)、交换分区(/swap)、/根分区、/home分区。 具体参数设置&#xff1a; 1. EFI系统分…

02. 【Android教程】开发环境搭建

在学习 Android 应用开发之前&#xff0c;我们先要完成环境的搭建&#xff0c;它将帮助我们将 Java 代码编译打包生成最终的 Android 安装包。本教程在 Mac 下完成安装&#xff0c;Windows 和 Linux 步骤类似&#xff0c;不同之处会着重区分。 1. 文件清单 Java SE Developmen…

JVM的知识

什么是JVM 1.JVM&#xff1a; JVM其实就是运行在 操作系统之上的一个特殊的软件。 2.JVM的内部结构&#xff1a; &#xff08;1&#xff09;因为栈会将执行的程序弹出栈。 &#xff08;2&#xff09;垃圾99%的都是在堆和方法区中产生的。 类加载器&#xff1a;加载class文件。…

芯片中小公司ERP系统的业务流程:揭秘数字化管理的新篇章

随着信息技术的飞速发展&#xff0c;ERP(企业资源规划)系统已成为众多企业实现数字化管理的重要工具。对于芯片中小公司而言&#xff0c;ERP系统更是提升运营效率、优化资源配置的关键所在。那么&#xff0c;芯片中小公司的ERP系统究竟是如何运作的呢?让我们一同揭开其业务流程…

Spatialite坐标投影并计算面积

将坐标转为WGS_1984_UTM_Zone_48N&#xff08;32648&#xff09;后再计算其面积&#xff1a; -- 转换坐标系并计算面积&#xff08;平方米&#xff09; SELECT ST_Area(ST_Transform(GeomFromText(POLYGON((106.763 26.653, 106.763 26.626, 106.815 26.625, 106.809 26.666, …

我们使用 Postgres 构建多租户 SaaS 服务时踩的坑

原文 Our Multi-tenancy Journey with Postgres Schemas and Apartment。这篇和之前发出的「如何使用 Postgres 对一个多租户应用分片」相呼应。 多租户 (Multip-tenancy) 是当下的热门话题。我对多租户应用程序的定义是一个能够服务于多个客户的软件系统&#xff0c;每个客户都…

有名的爬虫框架 colly 的特性及2个详细采集案例

一. Colly概述 前言&#xff1a;colly 是 Go 实现的比较有名的一款爬虫框架&#xff0c;而且 Go 在高并发和分布式场景的优势也正是爬虫技术所需要的。它的主要特点是轻量、快速&#xff0c;设计非常优雅&#xff0c;并且分布式的支持也非常简单&#xff0c;易于扩展。 框架简…

javaSSM游泳馆日常管理系统IDEA开发mysql数据库web结构计算机java编程maven项目

一、源码特点 IDEA开发SSM游泳馆日常管理系统是一套完善的完整企业内部系统&#xff0c;结合SSM框架和bootstrap完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用SSM框架&#xff08;MVC模式开发&#xff09;MAVEN方式加载&#xff0c;系统具有完整的源代码和…

疫情居家办公OA系统设计与实现| Mysql+Java+ B/S结构(可运行源码+数据库+设计文档)

本项目包含可运行源码数据库LW&#xff0c;文末可获取本项目的所有资料。 推荐阅读100套最新项目 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 2024年56套包含java&#xff0c;…

day04套餐管理模块所有业务功能代码开发

目录 1. 新增套餐1.1 需求分析和设计1.2 代码实现1.2.1 DishController1.2.2 DishService1.2.3 DishServiceImpl1.2.4 DishMapper1.2.5 DishMapper.xml1.2.6 SetmealController1.2.7 SetmealService1.2.8 SetmealServiceImpl1.2.9 SetmealMapper1.2.10 SetmealMapper.xml1.2.11…

shell脚本入门练习(非常详细)零基础入门到精通,收藏这一篇就够了

【脚本1】打印形状 打印等腰三角形、直角三角形、倒直角三角形、菱形 #!/bin/bash \# 等腰三角形 read \-p "Please input the length: " n for i in \seq 1 $n\ do for ((j\$n;j>i;j--)) do echo \-n " " done for m in \seq 1 $i\ do…

希尔伯特-黄变换(Hilbert-Huang Transform, HHT)详解

目录 经验模态分解&#xff08;EMD&#xff09; 希尔伯特谱分析&#xff08;HSA&#xff09; 定义 连续时信号的Hilbert变换定义 离散时信号的Hilbert变换定义 解析信号定义&#xff1a; 解析信号的傅里叶变换 解析信号的重要意义 解析信号的属性 希尔伯特--黄变换&#xff08;…

LabVIEW电动汽车直流充电桩监控系统

LabVIEW电动汽车直流充电桩监控系统 随着电动汽车的普及&#xff0c;充电桩的安全运行成为重要议题。通过集成传感器监测、单片机技术与LabVIEW开发平台&#xff0c;设计了一套电动汽车直流充电桩监控系统&#xff0c;能实时监测充电桩的温度、电压和电流&#xff0c;并进行数…

Geohash编码

1. 简介 地理位置&#xff08;经纬度坐标对&#xff09;编码为字母数字串&#xff0c;将空间分为网格形状每个网格使用一个编码&#xff0c;是Z阶曲线的众多应用之一。 2. 编码原理 &#xff08;1&#xff09; 首先根据区域划分的精度大小选择Geohash的字符串的长度&#xf…

[DDD] ValueObject的一种设计落地及应用

目录 前言一、ValueObject二、设计2.1 接口2.2 单一值ValueObject2.3 单一字符串ValueObject 三、实现3.1 示例3.1.1 PhoneNumber3.1.2 SocialCreditCode 四、使用4.1 异常处理4.2 Json 反/序列化4.2.1 请求体4.2.2 HTTP接口4.2.3 用例 4.3 JPA/MyBatis4.3.1 Converter或TypeHa…

HarmonyOS实战开发-如何使用首选项能力实现一个简单示例。

介绍 本篇Codelab是基于HarmonyOS的首选项能力实现的一个简单示例。实现如下功能&#xff1a; 创建首选项数据文件。将用户输入的水果名称和数量&#xff0c;写入到首选项数据库。读取首选项数据库中的数据。删除首选项数据文件。 最终效果图如下&#xff1a; 相关概念 首选…

第二证券|基本面向好预期强化 全球资本加紧布局A股

开年以来&#xff0c;在我国经济上升向好的态势持续稳固增强的大布景下&#xff0c;结合各方努力&#xff0c;A股商场企稳上升痕迹明显。受一系列稳定商场预期政策出台的加持&#xff0c;全球本钱正在加速布局A股商场。 业界人士指出&#xff0c;当时我国本钱商场依然具有明显…