【C++】哈希表 --- 闭散列版本的实现

news2024/12/23 4:39:22

在这里插入图片描述

在无人问津日子里
正是登峰造极的好时机
——《人民日报》

哈希表 --- 闭散列版本的实现

  • 1 C++中的哈希表
  • 2 哈希表底层
    • 2.1 功能
    • 2.1 哈希冲突
    • 2.3 开散列与闭散列
  • 3 闭散列版本的实现
    • 3.1 框架搭建
    • 3.2 仿函数设计
    • 3.3 插入函数
    • 3.4 查找函数
    • 3.5 删除函数
  • Thanks♪(・ω・)ノ谢谢阅读!!!
  • 下一篇文章见!!!

1 C++中的哈希表

哈希表(Hash Table)是一种数据结构,它通过哈希函数将键映射到表中的一个位置来访问记录,支持快速的插入和查找操作。

哈希表的概念最早可以追溯到1953年,由H. P. Luhn提出。他首次描述了使用哈希函数来加速数据检索的过程。随后,这一概念在数据库管理系统和编程语言中得到广泛应用。

在计算机科学中,哈希表的发展与算法和数据处理的需求紧密相关。随着计算机硬件性能的提升和数据量的爆炸性增长,哈希表作为一种高效的数据结构,在软件工程、数据库系统、网络搜索引擎等领域扮演着重要角色。

在C++中unordered系列关联式容器是哈希表

在C++98中,STL提供了底层为红黑树结构的一系列关联式容器,在查询时效率可达到 l o g 2 N log_2N log2N,即最差情况下需要比较红黑树的高度次,当树中的节点非常多时,查询效率也不理想。最好的查询是,进行很少的比较次数就能够将元素找到,因此在C++11中,STL又提供了4个unordered系列的关联式容器,这四个容器与红黑树结构的关联式容器使用方式基本类似,只是其底层结构不同

— 使用文档

2 哈希表底层

2.1 功能

顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即O( l o g 2 N log_2 N log2N),搜索的效率取决于搜索过程中元素的比较次数。

而我们希望的理想搜索方法应该是 :可以不经过任何比较,一次直接从表中得到要搜索的元素。

如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码key之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素。

那么当向该结构中:

  • 插入元素:只需要根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放
  • 搜索元素:直接对对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功

2.1 哈希冲突

在这里插入图片描述

对于两个数据元素的关键字 k i k_i ki k j k_j kj(i != j),有 k i k_i ki != k j k_j kj,但有:Hash( k i k_i ki) ==Hash( k j k_j kj),即:不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞。

哈希冲突可能是哈希函数引起的:
哈希函数设计原则:

  • 哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有m个地址时,其值域必须在0到m-1之间
  • 哈希函数计算出来的地址能均匀分布在整个空间中
  • 哈希函数应该比较简单

可见哈希函数时有可能造成哈希冲突的

把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”。发生哈希冲突该如何处理呢?
解决哈希冲突两种常见的方法是:闭散列和开散列

2.3 开散列与闭散列

该方式即为哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称为哈希表(Hash Table)(或者称散列表)

散列表分为闭散列和开散列,这是两种完全不同的方式,但是底层都是数组:

  • 闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去。那如何寻找下一个空位置呢?
    进行线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。
    比如上图中的场景,现在需要插入元素44,先通过哈希函数计算哈希地址,hashAddr为4,因此44理论上应该插在该位置,但是该位置已经放了值为4的元素,即发生哈希冲突。

    • 插入:通过哈希函数获取待插入元素在哈希表中的位置如果该位置中没有元素则直接插入新元素,如果该位置中有元素发生哈希冲突,使用线性探测找到下一个空位置,插入新元素
    • 删除:采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若直接删除元素会影响其他元素的搜索。比如删除元素4,如果直接删除掉,44查找起来可能会受影响。因此线性探测采用标记的伪删除法来删除一个元素
    • 线性探测优点:实现非常简单,
    • 线性探测缺点:空间利用率比较低,一旦发生哈希冲突,所有的冲突连在一起,容易产生数据“堆积”,即:不同关键码占据了可利用的空位置,使得寻找某关键码的位置需要许多次比较,导致搜索效率降低。可以使用二次探测法缓解。
  • 开散列:开散列又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链起来,各链表的头结点存储在哈希表中

在这里插入图片描述

3 闭散列版本的实现

下面我们来实现闭散列版本的哈希表

3.1 框架搭建

首先我们需要进行一个简单的框架搭建:

  1. 我们需要一个HashData类,来储存数据
  2. HashTable类底层是vector容器
  3. 因为会有不同类型的key,所以我们需要一个仿函数来将不同类型转换为size_t;
  4. 因为闭散列的删除不能直接删除节点,否则会导致线性探测失效,所以HashData类里需要记录状态!
pragma once
//----------哈希表模拟实现-----------
//版本一 --- 闭散列
#include<utility>
#include<iostream>
#include<vector>
using namespace std;

//节点状态
enum status
{
	EXIST,
	EMPTY,
	DELETE
};
//设计节点
template<class k , class v>
struct HashData
{
	HashData()
	{
		status = EMPTY;
	}
	//键值对
	pair<k, v> _kv;
	//状态
	status status;
};
// kv键值  , 仿函数解决不同类型key转换为size_t类型的下标
template<class k , class v , class Hash = HashFunc<k> >
class HashTable
{
public:
	HashTable()
	{
		_table.resize(10);
	}
private:
	//底层是vector容器
	vector<HashData<k , v>> _table;
	size_t _n;//有效数据个数
	Hash hs;
};

3.2 仿函数设计

仿函数的作用是将不同数据类型的key转换为可以使用的size_t类型。
对于可以直接显示类型转换的类型直接转换即可。而对于不能直接转换的类型(比如string)就要进行特殊处理了!

//设计仿函数 --- 适配不同数据类型的key
template<class K>
struct HashFunc
{
	//可以进行显示类型转换的直接转换!!!
	size_t operator()(const K& k)
	{
		return (size_t)k;
	}
};
//string不能进行直接转换,需要特化
template<>
struct HashFunc<string>
{
	//可以进行显示类型转换的直接转换!!!
	size_t operator()(const string& k)
	{
		size_t key = 0;
		for (auto s : k)
		{
			key *= 131;
			key += s;
		}
		return key;
	}
};

3.3 插入函数

  1. 首先插入之前要先检查是否在哈希表中已经有数据了
  2. 然后检查该次是否需要进行扩容
  3. 通过key值选取合适位置进行插入,有效个数加一
bool insert(pair<k,v> kv)
{
	//插入前先进行一个检查
	if (Find(kv.first)) return false;
	//是否需要扩容
	if (_n == _table.size() * 0.7)
	{
		//进行替换
		HashTable<k, v> newHT;
		newHT._table.resize(_table.size() * 2);
		//进行赋值
		for (auto s : _table)
			newHT.insert(s._kv);
		//进行替换!!!
		_table.swap(newHT._table);
	}
	//进行插入
	//hash地址
	int hashi = hs(kv.first)% _table.size();
	//寻找合适位置进行插入
	// 线性探测
	while (_table[hashi].status == EXIST)
	{
		hashi++;
		hashi %= _table.size();
	}
	//找到合适位置了进行插入
	_table[hashi]._kv = kv;
	_table[hashi].status = EXIST;
	_n++;
	return true;
}

3.4 查找函数

查找的逻辑很简单,通过key值锁定位置进行线性探测即可!

//查找
HashData<k , v>* Find(const k& Key)
{
	int hashi = hs(Key) % _table.size();
	while (_table[hashi].status != EMPTY)
	{
		if (Key == _table[hashi]._kv.first && _table[hashi].status == EXIST)
		{
			return &_table[hashi];
		}
		++hashi;
		hashi %= _table.size();
	}
	return nullptr;
}

3.5 删除函数

删除先通过key找到需要删除的数据
然后将状态设置为DELETE , 有效个数减一

//删除
bool Erase(const k& Key)
{
	//int hashi = Key % _table.size();

	//while (_table[hashi].status != EMPTY)
	//{
	//	if (Key == _table[hashi]._kv.first && _table[hashi].status == EXIST)
	//	{
	//		_table[hashi].status = DELETE;
	//		--_n;
	//		return true;
	//	}
	//	++hashi;
	//	hashi %= _table.size();
	//}
	//return false;
	
	//简单版
	HashData<k , v>* ret = Find(Key);
	if (ret == nullptr)
	{
		return false;
	}
	else
	{
		ret->status = DELETE;
		--_n;
		return true;
	}
}

这样我们就实现了闭散列的哈希表!!!

Thanks♪(・ω・)ノ谢谢阅读!!!

下一篇文章见!!!

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

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

相关文章

windows 10 安装tcping 使用教程

1 官网下载:tcping下载 2 复制tcping 到win10系统目录C:\Windows\System32 3 tcping 网址测试,可以指定端口 4 tcping 测试端口联通 5 tcping http模式

LeetCode 算法: 合并 K 个升序链表 c++

原题链接&#x1f517;&#xff1a;合并 K 个升序链表 难度&#xff1a;困难⭐️⭐️⭐️ 题目 给你一个链表数组&#xff0c;每个链表都已经按升序排列。 请你将所有链表合并到一个升序链表中&#xff0c;返回合并后的链表。 示例 1&#xff1a; 输入&#xff1a;lists […

Hugging Face Accelerate 两个后端的故事:FSDP 与 DeepSpeed

社区中有两个流行的零冗余优化器 (Zero Redundancy Optimizer&#xff0c;ZeRO)算法实现&#xff0c;一个来自DeepSpeed&#xff0c;另一个来自PyTorch。Hugging FaceAccelerate对这两者都进行了集成并通过接口暴露出来&#xff0c;以供最终用户在训练/微调模型时自主选择其中之…

Python | Leetcode Python题解之第191题位1的个数

题目&#xff1a; 题解&#xff1a; class Solution:def hammingWeight(self, n: int) -> int:ret 0while n:n & n - 1ret 1return ret

PAE:从潮流报告中提炼有效产品属性

本文将介绍PAE&#xff0c;一种用于包含 PDF格式的文本和图像的产品属性提取算法。目前大部分的方法侧重于从标题或产品描述中提取属性&#xff0c;或利用现有产品图像中的视觉信息。与之前的工作相比&#xff0c;PAE从潮流趋势报告的PDF文件中提取属性&#xff0c;提取的属性包…

ISO26262标准

什么是ISO26262&#xff1f; ISO 26262(国际功能安全标准)是一个涵盖整个汽车产品开发过程的汽车功能安全标准。ISO 26262继承或改编自工业自动化行业的安全要求标准IEC61508&#xff0c;但专门为汽车行业量身定制。最新版本是ISO26262-1:2018。 它包括诸如需求分析、安全分析…

一个简单的文件上传功能

代码如下&#xff1a; PostMapping("/upload")public ResponseEntity<String> handleFileUpload(RequestParam(value "uploadDirectory") String uploadDirectory,RequestParam("fileName") MultipartFile fileName) {try {// 确保文件不…

乱扔垃圾自动识别摄像头

如今&#xff0c;随着城市化进程的加快和人们生活水平的提高&#xff0c;环境保护和城市美观成为社会关注的焦点。乱扔垃圾问题长期困扰着城市管理者和居民&#xff0c;给城市环境卫生带来严重挑战。为了有效解决这一问题&#xff0c;乱扔垃圾自动识别摄像头应运而生&#xff0…

nvm-desktop window安装,支持动态切换nodejs版本

一、安装 nvm-desktop 概述 1 、卸载干净笔记的nodejs 和nodejs的环境变量 2、安装 nvm-desktop 软件 3、配置环境变量 4、测试功能 # 此时已安装完成 其他&#xff1a;常见nodejs的问题解决参考&#xff1a;官网 mac 安装教程 https://github.com/1111mp/nvm-desktop/blob/…

大模型微调实战之基于星火大模型的群聊对话分角色要素提取挑战赛:Task01:跑通Baseline

目录 0 背景1 环境配置1.1 下载包1.2 配置密钥1.3 测试模型 2 解决问题2.1 获取数据2.2 设计Prompt2.2 设计处理函数2.3 开始提取 附全流程代码 0 背景 Datawhale AI夏令营第二期开始啦&#xff0c;去年有幸参与过第一期&#xff0c;收获很多&#xff0c;这次也立马参与了第二…

基于Python的求职招聘管理系统【附源码】

摘 要 随着互联网技术的不断发展&#xff0c;人类的生活已经逐渐离不开网络了&#xff0c;在未来的社会中&#xff0c;人类的生活与工作都离不开数字化、网络化、电子化与虚拟化的数字技术。从互联网的发展历史、当前的应用现状和发展趋势来看&#xff0c;我们完全可以肯定&…

AI视界引擎 | ​基于 YOLOv8 和计算机视觉 CV 的实时识别系统!

本文来源公众号“AI视界引擎”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;​基于 YOLOv8 和计算机视觉 CV 的实时识别系统&#xff01; 技术进步和创新正在尽可能地推进作者的日常生活&#xff0c;但仍有很大一部分社会群体因为…

OverTheWire Bandit 靶场通关解析(中)

介绍 OverTheWire Bandit 是一个针对初学者设计的网络安全挑战平台&#xff0c;旨在帮助用户掌握基本的命令行操作和网络安全技能。Bandit 游戏包含一系列的关卡&#xff0c;每个关卡都需要解决特定的任务来获取进入下一关的凭证。通过逐步挑战更复杂的问题&#xff0c;用户可…

poi-tl 生成 word 文件(插入文字、图片、表格、图表)

文章说明 本篇文章主要通过代码案例的方式&#xff0c;展示 poi-tl 生成 docx 文件的一些常用操作&#xff0c;主要涵盖以下内容 &#xff1a; 插入文本字符&#xff08;含样式、超链接&#xff09;插入图片插入表格引入标签&#xff08;通过可选文字的方式&#xff0c;这种方…

昇思MindSpore学习笔记3--张量 Tensor

一、张量Tensor概念 矢量、标量和其他张量的计算函数&#xff0c;有内积、外积、线性映射以及笛卡儿积等 张量坐标在 n 维空间内&#xff0c;有 nr 个分量 每个分量都是坐标的函数,变换时每个坐标分量都按规则作线性变换 张量是一种特殊的数据结构&#xff0c;类似于数组和…

haproxy实现代理和负载均衡

HaProxy介绍&#xff1a; haproxy是法国开发者威利塔罗在2000年使用C语言开发的一个开源软件&#xff0c;是一款具备高并发(一万以上)、高性能的TCP和HTTP负载均衡器&#xff0c;支持基于cookie的持久性&#xff0c;自动故障切换&#xff0c;支持正则表达式及web状态统计&…

狼人杀系列

目录 杀人游戏&#xff08;天黑请闭眼&#xff09; &#xff08;1&#xff09;入门版 &#xff08;2&#xff09;标准版 &#xff08;3&#xff09;延伸版——百度百科 &#xff08;3.1&#xff09;引入医生和秘密警察 &#xff08;3.2&#xff09;引入狙击手、森林老人和…

学习gateway网关路由时遇到的问题

遇到这个问题先别慌&#xff0c;我们首先要检查是哪里出问题了&#xff0c;从报错信息中我们可以看到&#xff0c;他说 Unable to find GatewayFilterFactory with name -AddRequestHeader 找不到这个路由过滤器&#xff0c;所以导致网关设置失败&#xff0c;从这条信息上我…

Mac可以读取NTFS吗 Mac NTFS软件哪个好 mac ntfs读写工具免费

在跨操作系统环境下使用外部存储设备时&#xff0c;特别是当Windows系统的U盘被连接到Mac电脑时&#xff0c;常常会遇到文件系统兼容性的问题。由于Mac OS原生并不完全支持对NTFS格式磁盘的读写操作&#xff0c;导致用户无法直接在Mac上向NTFS格式的U盘或硬盘写入数据。下面我们…

web学习笔记(六十九)vue2

目录 1. vue2创建脚手架项目 2.vue2如何关闭eslint 1. vue2创建脚手架项目 &#xff08;1&#xff09;在cmd窗口输入npm install -g vue/cli命令行&#xff0c;快速搭建脚手架。 &#xff08;2&#xff09; 创建vue2项目 &#xff08;3&#xff09; 选择配置项目&#xff0c…