C++ 哈希 开放定址法

news2024/11/17 17:40:51

哈希算法

哈希,是一种算法思想吗,它的核心是映射,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称为哈希表(Hash Table)(或者称散列表)

在STL 中,提供了两个使用哈希底层实现的容器 unordered_set 和 unordered_map,unordered是无序的意思。它们存储数据的方式类似于 set 和 map,但缺少了排序的功能。在性能测试中,debug 环境下,unordered_set 和 unordered_map性能都是优于set和map 的,只有在 release 环境并且插入数据有序的情况下,set和map的性能才略微更优一点。

因为map和set的底层是红黑树,如果数据有序,那么红黑树就会一直往左边或右边插入节点,反复旋转(旋转被release 给优化了),导致红黑树建立后变得相对均衡了很多。查找、删除就变得更高效了。

总体来说,在不排序的情况下,unordered_map和unordered_set对于普通数存储数据处理的效率较好一些。

直接定址法

  • hashi = key % len

每个数据的 key 与位置之间建立一个关系,通过除留余数法计算出每个 key 对应得 hashi,这个方法也是计数排序的思想。

但直接定址法会有问题,可能会出现一些问题:不同的值映射到相同的位置上去。这种现象叫哈希冲突哈希碰撞

开放定址法

开放定址法也叫闭散列,适用于当前位置被占用,开放空间里面按照某种规则,找一个没被占用的位置存储。

一般有线性探测二次探测这两种

  • 线性探测:hashi + i (i >= 0)
  • 二次探测:hashi  +  i ^ 2 (i >= 0) 

负载因子(空间占有率)= 存储数据的个数 / 空间大小,闭散列的负载因子一般是小于等于 0.7。负载因子太大会导致冲突增加;太小虽然会减少冲突,但会导致空间利用率降低。

使用开放定址法实现哈希表

使用 vector 来建立哈希表,每个位置中存储一个类对象。这个类对象中包含两个变量参数,一个是哈希表中需要存储的数据类型,另一个是这个位置的状态:EMPTY(空)、EXIST(有数据)、DELETE(删除过的),为什么还要设置一个删除状态呢?

有可能删除后,在 find 的时候,之前插入位置的数据已经被删除了,但有些冲突的数据被调到了其他位置,因此遇到 DELETE 状态要继续寻找。

类的定义

定义三个模板参数,K 和 V分别做 pair的参数,再定义一个带缺省参数的仿函数,这个仿函数主要是为了计算 hashi 而准备的。

仿函数分为两个,一个针对整形类型;一个针对字符串类型。

其实并不是针对整形。这种定义方法,是在使用者没有自己定义仿函数的情况下使用的,如果第一个参数 K 传的是除 string 外其他类型,而也没有传仿函数时,就会默认去调用第一个仿函数。

而当第一个参数 K 传的是 string 类型时,就会调用针对 string 的模板特化(特化不能单独存在),这样,当用户传string 类型的参数时,也不用自己显示传仿函数,只需要传 Key 和 Value 即可。当用户需要给 Key 传其他类型的时候,就需要自己调用写函数并调用了。

接口定义

  • 插入:先算 hashi,利用除留余数法,如果位置重复就使用线性探测或二次探测来解决问题,状态需要变为 EXIST。当负载因子等于 0.7 的时候,就要进行扩容。
  • 扩容:只能进行异地扩容!异地扩容,建立一个新哈希表将原来空间中已经存在数据的位置再在新空间中重新插入,然后将两个哈希表交换即可。新哈希表的空间会在函数结束后自动释放。
  • 删除:使用伪删除法。将数据个数减1,将状态变为 DELETE。
  • 查找:先使用查找值计算 hashi,然后根据与 hashi 位置的数据与查找值进行比对,判断是否相等,如果不相等,根据线性探测或者二次探测往后依次找。找到了返回存储数据类型的指针,找不到返回空指针。

代码实现

#pragma once
#include<vector>
#include<string>
#include<iostream>
using namespace std;
namespace zyb
{
	enum States
	{
		EMPTY,
		EXIST,
		DELETE
	};
	template<class  K, class V>
	struct HashData
	{
		pair<K, V> _kv;
		States _s = EMPTY;
	};
	template<class K>
	struct KeyOfi
	{
		size_t operator()(const K& key)
		{
			return (size_t)key;
		}
	};
	template<>
	struct KeyOfi<string>
	{
		size_t operator()(const string& key)
		{
			size_t hashi = 0;
			for (size_t i = 0; i < key.size(); i++)
			{
				hashi *= 31;
				hashi += key[i];
			}
			return hashi;
		}
	};
	template<class  K, class V, class Hash= KeyOfi<K>>
	class HashTable
	{
	public:
		Hash ky;
		HashTable()
		{
			_tables.resize(10);
		}
		HashData<K, V>* find(const K& key)
		{
			size_t hashi = ky(key) % _tables.size();
			while (_tables[hashi]._s != EMPTY)
			{
				if (_tables[hashi]._s == EXIST && _tables[hashi]._kv.first == key)
				{
					return &_tables[hashi];
				}
				++hashi;
				hashi %= _tables.size();
			}
			return nullptr;
		}
		bool erase(const K& key)
		{
			HashData<K, V>* ret = find(key);
			if (ret)
			{
				ret->_s = DELETE;
				_n--;
				return true;
			}
			return false;
		}
		bool insert(const pair<K, V>& kv)
		{
			if (find(kv.first))
			{
				return false;
			}
			// 平衡因子设置为 0.7
			if (_n * 10 / _tables.size() == 7)
			{
				// 扩容
				HashTable<K, V> newht;
				newht._tables.resize(2 * _tables.size());
				for (size_t i = 0; i < _tables.size(); i++)
				{
					if (_tables[i]._s == EXIST)
					{
						newht.insert(_tables[i]._kv);
					}
				}
				_tables.swap(newht._tables);
			}
			 
			size_t hashi = ky(kv.first) % _tables.size();
			while (_tables[hashi]._s == EXIST)
			{
				++hashi;
				hashi %= _tables.size();
			}
			_tables[hashi]._kv = kv;
			_tables[hashi]._s = EXIST;
			_n++;
			return true;
		}
		void Print()
		{
			for (int i = 0; i < _tables.size(); i++)
			{
				if (_tables[i]._s == EXIST)
				{
					//printf("[%d]->%d\n", i, _tables[i]._kv.first);
					cout << "[" << i << "]->" << _tables[i]._kv.first << ":" << _tables[i]._kv.second << endl;
				}
				else if (_tables[i]._s == EMPTY)
				{
					printf("[%d]->\n", i);
				}
				else
				{
					printf("[%d]->D\n", i);
				}
			}

			cout << endl;
		}
		private:
			vector<HashData<K, V>> _tables;
			size_t _n = 0;
	};
}

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

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

相关文章

西瓜书读书笔记整理(十二) —— 第十二章 计算学习理论(下)

第十二章 计算学习理论&#xff08;下&#xff09; 12.4 VC 维&#xff08;Vapnik-Chervonenkis dimension&#xff09;12.4.1 什么是 VC 维12.4.2 增长函数&#xff08;growth function&#xff09;、对分&#xff08;dichotomy&#xff09;和打散&#xff08;shattering&…

Python算法题集_合并区间

本文为Python算法题集之一的代码示例 题目56&#xff1a;合并区间 说明&#xff1a;以数组 intervals 表示若干个区间的集合&#xff0c;其中单个区间为 intervals[i] [starti, endi] 。请你合并所有重叠的区间&#xff0c;并返回 一个不重叠的区间数组&#xff0c;该数组需…

CANoe实际项目中文件夹的规划

本人&#xff0c;之前设计了一个CANoe工程&#xff0c;由于工程设计之初没有设计好文档的归纳分类&#xff0c;导致文件查找起来非常费劲。 为了避免以后出现文件混乱&#xff0c;不可查找的问题&#xff0c;故特此归纳说明。 建立工程时&#xff1a; 第1步就应该设计好文档…

品牌定位传播之道:公关、广告与定位原则的结合

​在当今商业环境中&#xff0c;品牌传播的重要性日益凸显。一个成功的品牌传播策略不仅能提升品牌知名度和美誉度&#xff0c;还能在消费者心智中建立稳固的地位。本文将深入探讨公关、广告和定位原则在品牌传播中的作用&#xff0c;以及迅腾文化如何助力品牌传播价值。 一、…

miniReact<一>

一、工程化配置 1.1 目录结构 1.1.1 Multi-repo VS Mono-repo Multi-repo 每个库有自己独立的仓库&#xff0c;逻辑清晰&#xff0c;协同管理复杂 Mono-repo 很方便管理不同独立的库的生命周期&#xff0c;会有更高的操作复杂度 项目有很多包&#xff0c;同时管理多个不同的…

iOS开发Xcode中的ld64和-ld_classic是什么意思

在iOS应用程序开发中&#xff0c;Xcode是一款广泛使用的集成开发环境&#xff08;IDE&#xff09;&#xff0c;而链接器是构建应用程序的关键组成部分之一。在Xcode中&#xff0c;我们常常会遇到两个重要的概念&#xff1a;ld64和-ld_classic。它们分别代表了默认链接器和经典链…

Shell脚本之 -------------免交互操作

一、Here Document 1.Here Document概述 Here Document 使用I/O重定向的方式将命令列表提供给交互式程序 Here Document 是标准输 入的一种替代品&#xff0c;可以帮助脚本开发人员不必使用临时文件来构建输入信息&#xff0c;而是直接就地 生产出一个文件并用作命令的标准…

JVM篇----第十八篇

系列文章目录 文章目录 系列文章目录前言一、什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”?二、对象分配规则三、描述一下JVM加载class文件的原理机制?前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到…

springboot综合案例(一)

文章目录 前言项目开发流程需求分析库表设计编码环节环境搭建mybatis的配置jsp模版引擎的配置日志的配置基本项目工程的配置 功能实现用户注册实现验证码功能实现用户注册 用户登录功能员工列表实现员工信息增删查改员工增加信息员工修改信息删除员工信息 前言 我具体用一个小…

InputNumber数字输入框(antd-design组件库)简单使用

1.InputNumber数字输入框 通过鼠标或键盘&#xff0c;输入范围内的数值。 2.何时使用 当需要获取标准数值时。 组件代码来自&#xff1a; 数字输入框 InputNumber - Ant Design 3.本地验证前的准备 参考文章【react项目antd组件-demo:hello-world react项目antd组件-demo:hello…

震动传感器详解

当涉及到物体的震动检测和感应时&#xff0c;震动模块成为一种常见且实用的工具。这种小巧而功能强大的设备可以用于各种应用&#xff0c;从智能家居到安防系统&#xff0c;再到工业自动化等领域。通过感知和转换物体震动为电信号&#xff0c;震动模块在许多方面都发挥着重要的…

chromedriver安装和环境变量配置

chromedriver 1、安装2、【重点】环境变量配置&#xff08;1&#xff09;包的复制&#xff1a;&#xff08;2&#xff09;系统环境变量配置 3、验证 1、安装 网上随便搜一篇chromedriver的安装文档即可。这里是一个快速链接 特别提醒&#xff1a;截止2024.1.30&#xff0c;chr…

Springboot+vue的健身房管理系统(有报告)。Javaee项目,springboot vue前后端分离项目

演示视频&#xff1a; Springbootvue的健身房管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot vue前后端分离项目 项目介绍&#xff1a; 本文设计了一个基于Springbootvue的前后端分离的健身房管理系统&#xff0c;采用M&#xff08;model&#xf…

老师和老师的区别在哪里

“老师和老师的区别在哪里&#xff1f;”这真是个好问题。有时我会想&#xff0c;是不是因为自己多读了几本书&#xff0c;或者多经历了一些世事&#xff0c;就能更好地胜任教育工作。但实际上&#xff0c;老师和老师的区别&#xff0c;并不仅仅在于经验和知识&#xff0c;更在…

语言革命:NLP与GPT-3.5如何改变我们的世界

文章目录 &#x1f4d1;前言一、技术进步与应用场景1.1 技术进步1.2 应用场景 二、挑战与前景三、伦理和社会影响四、实践经验五、总结与展望 &#x1f4d1;前言 自然语言处理&#xff08;Natural Language Processing&#xff0c;NLP&#xff09;是人工智能领域的一个重要分支…

【Linux】线程池的简易实现(懒汉模式)

文章目录 前言一、懒汉方式1.普通模式1.线程安全模式 二、源代码1.Task.hpp(要执行的任务)2.ThreadPool.hpp(线程池)3.Main.cpp 前言 线程池: 一种线程使用模式。线程过多会带来调度开销&#xff0c;进而影响缓存局部性和整体性能。而线程池维护着多个线程&#xff0c;等待着监…

生产问题排查系列——redis告警连接异常问题排查

项目背景 我们的项目使用redis的场景主要是有两种&#xff0c;一是使用redis缓存各种业务信息&#xff0c;二是使用redis做分布式锁。主要是引用了两个框架jedis和redisson。 Jedis是Redis的Java实现的客户端&#xff0c;其API提供了比较全面的Redis命令的支持&#xff1b; …

【linux】文本处理命令-grep、awk、sed使用(1)

作用&#xff1a; grep数据查找定位awk数据切片sed数据修改 类比SQL&#xff1a; grepselect *from tableawkselect field from tablesedupdate table set fieldnew where fieldold 一、grep 1.1 grep* Unix的grep家族包括grep、egrep和fgrep。egrep和fgrep的命令只跟g…

房屋租赁系统-java

思维导图&#xff1a;业务逻辑 类的存放&#xff1a; 工具类 Utility package study.houserent.util; import java.util.*; /***/ public class Utility {//静态属性。。。private static Scanner scanner new Scanner(System.in);/*** 功能&#xff1a;读取键盘输入的一个菜单…

STM32 RTC中断处理和低功耗模式优化技巧

在基于STM32的RTC应用中&#xff0c;中断处理和低功耗模式优化是非常重要的&#xff0c;可以提高系统的效率和节能。下面&#xff0c;我将介绍STM32 RTC中断处理和低功耗模式优化的技巧。 ✅作者简介&#xff1a;热爱科研的嵌入式开发者&#xff0c;修心和技术同步精进 ❤欢迎关…