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

在这里插入图片描述

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

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

  • 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♪(・ω・)ノ谢谢阅读!!!

下一篇文章见!!!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

# [0628] Task04 DQN 算法及进阶

easy-rl PDF版本 笔记整理 P6 - P8 joyrl 比对 补充 P7 - P8 相关 代码 整理 待整理 &#xff01;&#xff01; 最新版PDF下载 地址&#xff1a;https://github.com/datawhalechina/easy-rl/releases 国内地址(推荐国内读者使用)&#xff1a; 链接: https://pan.baidu.com/s/1i…

vue3 window.location 获取正在访问的地址,也可以通过useRoute来获取相关信息。

1、一般我们在开发的vue3项目的时候&#xff0c;地址是这样&#xff1a;http://192.168.1.101:3100/#/login 然后我们在布署完成以后一般是这样https://xxx.yyyyy.com/uusys/#/login 其实xxx可以是www&#xff0c;也可以是一个二级域名 yyyyy.com是域名&#xff0c;uusys一般…

Kafka~消息发送过程与ISR机制了解

消息发送过程 使用Kafka发送消息时&#xff0c;一般有两种方式分别是&#xff1a; 同步发送异步发送 同步发送时&#xff0c;可以在发送消息后&#xff0c;通过get方法等待消息结果&#xff0c;这种情况能够准确的拿到消息最终的发送结果&#xff0c;要么是成功、要么是失败…

前端路由管理

前端路由管理简介&#xff1a; 当谈到前端路由管理时&#xff0c;通常指的是在单页面应用程序&#xff08;SPA&#xff09;中管理页面间导航和URL的过程。路由管理器是一个工具&#xff0c;可以帮助前端开发者定义应用程序的不同视图之间的关系&#xff0c;同时能够响应URL的改…

Attention (注意力机制)

1. 背景&#xff1a; 字面的意思&#xff1a;给你一些东西(看见一个美女:).....)&#xff0c;你会注意什么&#xff1f; 大数据的时代下&#xff0c;有太多的数据&#xff0c;我们又该如何选择重要的数据呢&#xff1f; Attention 诞生了&#xff0c;但是又该如何去做呢(i.e., …

springboot在线考试 LW +PPT+源码+讲解

第三章 系统分析 3.1 可行性分析 一个完整的系统&#xff0c;可行性分析是必须要有的&#xff0c;因为他关系到系统生存问题&#xff0c;对开发的意义进行分析&#xff0c;能否通过本系统来补充线下在线考试管理模式中的缺限&#xff0c;去解决其中的不足等&#xff0c;通过对…

[OtterCTF 2018]Play Time

还是这个程序 。。要找到游戏名字查看 进程 psscan pstree pslist 0x000000007d686b30 Rick And Morty 3820 2728 0x000000000b59a000 2018-08-04 19:32:55 UTC0000 0x000000007d7cb740 LunarMS.exe 708 2728 0x00000000731cb000 2018-08-04 19:27:39 UTC0000…

嵌入式Linux系统编程 — 4.7 regcomp、regexec、regfree正则表达式函数

目录 1 为什么需要正则表达式 2 正则表达式简介 3 正则表达式规则 4 regcomp、regexec、regfree函数 4.1 函数介绍 4.2 URL格式案例 1 为什么需要正则表达式 在许多的应用程序当中&#xff0c; 有这样的应用场景&#xff1a; 给定一个字符串&#xff0c;检查该字符串是否…

Spring学习01-[Spring实现IOC的几种方式]

Spring实现IOC的几种方式 基于xml实现Spring的IOC基于注解实现Spring的IOC基于JavaConfig实现的Spring的IOC基于SpringBoot实现Spring的IOC 基于xml实现Spring的IOC 引入spring核心依赖 <!--spring核心容器--><dependency><groupId>org.springframework<…

14 卡尔曼滤波及代码实现

文章目录 14 卡尔曼滤波及代码实现14.0 基本概念14.1 公式推导14.2 代码实现 14 卡尔曼滤波及代码实现 14.0 基本概念 卡尔曼滤波是一种利用线性系统状态方程&#xff0c;通过系统输入输出观测数据&#xff0c;对系统状态进行最优估计的算法。由于观测数据包括系统中的噪声和…

【智能制造-4】机器人控制器

机器人控制器中分哪几个模块&#xff1f; 机器人控制器通常由以下几个主要模块组成: 运动控制模块: 负责机器人各轴电机的位置、速度、加速度等控制 实现机器人末端执行器的精确定位和运动控制传感器接口模块: 负责机器人各种传感器信号的采集和处理 为运动控制、环境感知等提…

实用的vueuseHooks,提高编码效率

文章目录 写在前面vueuse 官网安装HooksuseStorage [地址](https://vueuse.org/core/useStorage/)传统方法数据持久化 举例子传统持久化的弊端useStorage 数据持久化 举例子使用useStorage 更改存储数据使用useStorage 删除存储数据 useScriptTag [地址](https://vueuse.org/co…

Detailed Steps for Troubleshooting ORA-00600 [kdsgrp1] (文档 ID 1492150.1)

Detailed Steps for Troubleshooting ORA-00600 [kdsgrp1] (文档 ID 1492150.1)​编辑转到底部 In this Document Purpose Troubleshooting Steps References APPLIES TO: Oracle Database - Enterprise Edition Oracle Database Cloud Schema Service - Version N/A and lat…

鸿蒙开发Ability Kit(程序框架服务):【选择申请权限的方式】

选择申请权限的方式 应用在访问数据或者执行操作时&#xff0c;需要评估该行为是否需要应用具备相关的权限。如果确认需要目标权限&#xff0c;则需要在应用安装包中申请目标权限。 每一个权限的权限等级、授权方式不同&#xff0c;申请权限的方式也不同&#xff0c;开发者在…

41割队伍

上海市计算机学会竞赛平台 | YACSYACS 是由上海市计算机学会于2019年发起的活动,旨在激发青少年对学习人工智能与算法设计的热情与兴趣,提升青少年科学素养,引导青少年投身创新发现和科研实践活动。https://www.iai.sh.cn/problem/387 题目描述 给定 𝑛n 个数字 𝑎1,�…

MySQL高级-MVCC-基本概念(当前读、快照读)

文章目录 1、MVCC基本概念1.1、当前读1.1.1、创建表 stu1.1.2、测试 1.2、快照读 1、MVCC基本概念 全称Multi-Version Concurrency Control&#xff0c;多版本并发控制。指维护一个数据的多个版本&#xff0c;使得读写操作没有冲突&#xff0c;快照读为MySQL实现MVCC提供了一个…

网易云音乐数据爬取与可视化分析系统

摘要 本系统采用Python语言&#xff0c;基于网易云音乐&#xff0c;通过数据挖掘技术对该平台的音乐数据进行了深入的研究和分析&#xff0c;旨在挖掘出音乐市场的规律&#xff0c;为音乐人、唱片公司、音乐爱好者等提供数据支持。系统的开发意义在于&#xff1a;一方面为音乐…

flink 处理函数和流转换

目录 处理函数分类 概览介绍 KeydProcessFunction和ProcessFunction 定时器TimeService 窗口处理函数 多流转换 分流-侧输出流 合流 联合&#xff08;Uniion&#xff09; 连接&#xff08;connect&#xff09; 广播连接流&#xff08;BroadcatConnectedStream&#xf…

大模型微调实战之基于星火大模型的群聊对话分角色要素提取挑战赛: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;这次也立马参与了第二…

昇思MindSpore学习笔记5--数据变换Transforms

摘要&#xff1a; 昇思MindSpore的数据变换&#xff0c;包括通用变换Common Transforms、图像变换Vision Transforms、标准化Normalize、文本变换Text Transforms、匿名函数变换Lambda Transforms。 一、数据变换Transforms概念 原始数据需预处理后才能送入神经网络进行训练…