哈希表(一)—— 闭散列 / 开放地址法的模拟实现

news2025/1/19 14:18:42

哈希表的基本思路是通过某种方式将某个值映射到对应的位置,这里的采取的方式是除留余数法,即将原本的值取模以后再存入到数组的对应下标,即便存入的值是一个字符串,也可以根据字符串哈希算法将字符串转换成对应的ASCII码值,然后再取模。

如果某个位置已经存了其他数据,那么就需要找一个空闲位置来存放当前数据,我们把这种方式称为闭散列(或者开放地址法)


         目录

1、线性探测法

(1) 基本思路

(2) 哈希表数据存储结构

(3) 查找实现

(4) 插入实现

(5) 移除实现

2、二次探测法

3、细节处理:遇到string或者自定义类型如何处理


1、线性探测法

(1) 基本思路

如果当前位置已经存了其他数据,那就看下一个位置是否空着,如果空着就存,被占了就继续看下一个位置,每次检索的步长为1,这种方式检索方式称为“线性探测法”。

假设第 start 个位置已经存了其他数据,那么后续检索的位置是 start + i,i = 0,1,2 ...

(2) 哈希表数据存储结构

哈希表每个位置可以只存一个数据,也可以存键值对,除此之外,还需要加一个状态标记。以查找为例,查找时,一般是遇到空就停下来,如果在找到某个数之前就遇到了空,就会导致后面的数据遍历不到;如果全部遍历一遍,效率太低。

因此,我们可以为表中的每个数据添加状态标记,只要不是 EMPTY 状态,就一直搜索。

enum Status
{
    EXIST,
    EMPTY,
    DELETE
};

template <class K, class V>
struct HashData
{
    pair<K, V> _kv;
    Status _status = EMPTY;     // 初始化缺省为 EMPTY
};

(3) 查找实现

HashData<K, V> *Find(const K &key)
{
    if (_tables.size() == 0)
    {
        return nullptr;
    }

    HashFunc hf;
    size_t start = hf(key) % _tables.size();    // 这里的 size() 是哈希表的有效数据个数
    size_t i = 0;
    size_t index = start;
    // 线性探测 or 二次探测
    while (_tables[index]._status != EMPTY)
    {
        if (_tables[index]._kv.first == key && _tables[index]._status == EXIST)
        {
            return &_tables[index];
        }

        ++i;
        
        index = start + i;        // 改为二次探测:index = start + i*i;

        index %= _tables.size();    // 避免超出哈希表的容量范围
    }

    return nullptr;
}

(4) 插入实现

插入实现时有两点需要额外注意:

  • 检查插入内容是否存在。这样做的目的是为了去重
  • 检查是否需要扩容。随着哈希表中数据个数的增加,哈希碰撞的次数也会增加,为了缓解哈希碰撞,当哈希表中有效数据个数达到 哈希表容量的 70% 以后,需要将哈希表扩容,并将原表的数据重新取模映射到新表。

bool Insert(const pair<K, V> &kv)
{
    // 判断插入内容是否存在
    HashData<K, V> *ret = Find(kv.first);
    if (ret)
    {
        return false;
    }

    // 负载因子到0.7,就扩容
    if (_tables.size() == 0 || _n * 10 / _tables.size() >= 7)
    {
        // 扩容
        size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;
        HashTable<K, V, HashFunc> newHT;
        newHT._tables.resize(newSize);
        
        // 非递归排布:遍历原表,把原表中的数据,重新按newSize映射到新表
        //vector<HashData<K, V>> newTables;
		//newTables.resize(newSize);
		//for (size_t i = 0; i < _tables.size(); ++i)
		//{
		//	
		//}
		//_tables.swap(newTables);

        // 递归排布
        for (size_t i = 0; i < _tables.size(); ++i)
        {
            if (_tables[i]._status == EXIST)
            {
                newHT.Insert(_tables[i]._kv);
            }
        }

        _tables.swap(newHT._tables);
    }

    HashFunc hf;
    size_t start = hf(kv.first) % _tables.size();
    size_t i = 0;
    size_t index = start;
    // 线性探测 or 二次探测
    while (_tables[index]._status == EXIST)
    {
        ++i;
        index = start + i;            // 改为二次探测:index = start + i*i;

        index %= _tables.size();
    }

    _tables[index]._kv = kv;
    _tables[index]._status = EXIST;
    ++_n;

    return true;
}

(5) 移除实现

bool Erase(const K &key)
{
    HashData<K, V> *ret = Find(key);
    if (ret == nullptr)
    {
        return false;
    }
    else
    {
        --_n;                     // 有效数据个数减1
        ret->_status = DELETE;    // 状态标记设为 DELETE(插入时遇到DELETE表示该位置可以存数据)
        return true;
    }
}

2、二次探测法

这里的二次指的是 二次方

  • 线性探测法每次检索的步长是1,检索的位置:start + i,i = 0,1,2 ...,
  • 二次探测法每次检索的步长不固定,每次检索的位置:start + i^2,i = 0,1,2 ...

二次线性探测法没有从根本上解决线性探测法的问题,只是不会让数据分布过于紧密。二次线性探测法的模拟实现只需要在线性探测法的基础上修改一处即可。

3、细节处理:遇到string或者自定义类型如何处理

如果哈希表中保存的数据类型是 string 或者 自定义结构体类型,按照上面这种逻辑,肯定会报错,但是我们可以使用仿函数来处理。下面以字符串的处理为例。

template<class K>
struct Hash
{
	size_t operator()(const K& key)
	{
		return key;        // 如果是内置类型(说明可以自己强转成整型),直接返回
	}
};

// 特化:如果传入的是字符串,就会调用模板特化处理
template<>
struct Hash < string >
{
	size_t operator()(const string& s)
	{
		// BKDR字符串哈希算法: 字符串中的每个字符的ASCII码值先乘以31 再加上 之前的值          
		size_t value = 0;
		for (auto ch : s)
		{
			value *= 31;
			value += ch;
		}
		return value;
	}
};

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

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

相关文章

这家十年磨剑的企业级存储厂商,为什么将分布式块存储也开源了?

只要提到企业级存储&#xff0c;任何成功的厂商无不以十年为单位的积累&#xff0c;才能实现真正的创新。当然&#xff0c;作为存储领域相对更为复杂的分布式块存储&#xff0c;存储创新公司一般都不太愿意碰它。原因很简单&#xff0c;在技术自研的道路上&#xff0c;更需要坐…

Nginx之限流

文章目录Nginx如何限流配置基本的限流处理突发无延迟的排队高级配置示例location包含多limit_req指令配置相关功能发送到客户端的错误代码指定location拒绝所有请求总结流量限制(rate-limiting)&#xff0c;是 Nginx 中一个非常实用&#xff0c;却经常被错误理解和错误配置的功…

JavaScript 数据处理 · 基本统计(文末附视频)

第 5 节 基本数据处理 基本统计 学习了如何对 JavaScript 中的数组数据进行操作之后&#xff0c;我们就要回到刚开始选择购买这本小册的目的了&#xff1a;使用 JavaScript 开发灵活的数据应用。既然说是数据应用&#xff0c;那么便离不开统计计算&#xff0c;而数组就可以说…

Android 设备自动重启分析[低内存]——MTK平台 debuglogger

大家有没有遇到和我一样的问题&#xff0c;android设备(我这里android 平板)用着用着突然就黑屏自动重启了&#xff0c;重启后一切正常&#xff0c;这个问题还是概率性的&#xff0c;复现都不好复现... 本人公司是做平板定制的&#xff0c;主要针对平板进行上网限制&#xff0c…

C语言进阶——字符函数

目录 一.前言 二.strlen 1.函数介绍 2.三种模拟实现 三.长度不受限制函数 1.strcpy 模拟实现 2.strcat 模拟实现 3.strcmp 模拟实现 四.长度受限制函数 1.strncpy 模拟实现 2.strncat 模拟实现 3.strncmp 模拟实现 五.字符串查找 1.strstr 模拟实现 2.st…

手把手教你快速搞定4个职场写作场景

“ 【写作能力提升】系列文章&#xff1a; 为什么建议你一定要学会写作? 手把手教你快速搞定 4 个职场写作场景 5 种搭建⽂章架构的⽅法”免费赠送! ”一、前言 Hello&#xff0c;我是小木箱&#xff0c;今天主要分享的内容是: 写作小白需要避免的五个写作误区和灵魂五问。 二…

好家伙谷歌翻译又不能用了(有效解决方法)

今天打开idea想翻译单词发现谷歌翻译又又又挂了。为什么挂掉&#xff0c;可能是那个ip节点太多人用了&#xff0c;我也不懂我就是一个小白。不bb了说一下解决方法。一、手动Ping可以连接的ip这里我使用的是&#xff1a;https://ping.chinaz.com然后我们输入&#xff1a; transl…

YoloV8简单使用

我们坐在阳光下&#xff0c;我们转眼间长大&#xff0c;Yolo系列都到V8了&#xff0c;来看看怎么个事。目标检测不能没有Yolo&#xff0c;就像西方不能没有耶路撒冷。这个万能的目标检测框架圈粉无数&#xff0c;经典的三段式改进也是改造出很多论文&#xff0c;可惜我念书时的…

论文投稿指南——中文核心期刊推荐(植物学)

【前言】 &#x1f680; 想发论文怎么办&#xff1f;手把手教你论文如何投稿&#xff01;那么&#xff0c;首先要搞懂投稿目标——论文期刊 &#x1f384; 在期刊论文的分布中&#xff0c;存在一种普遍现象&#xff1a;即对于某一特定的学科或专业来说&#xff0c;少数期刊所含…

English Learning - L1-12 情态动词 + 倒装 2023.1.12 周四

这里写目录标题9 情态动词9.2 may - mightmay(1) 表许可(2) 推测 --- 可能&#xff0c;或许&#xff08;50% 的概率&#xff09;might9.3 can - couldcan核心思维: 潜在的可能性&#xff0c;有某种知识和技能而能办到&#xff08;1&#xff09;能力&#xff08;有做 。。。的能…

java 计算机概述看这一篇文章就够了

第一章 计算机概述 第1节 计算机介绍 1广义上: 凡是可以帮助我们完成计算的工具统称为计算机(比如 算盘、计算器等...) 狭义上: 当前说计算机一般情况特指电子计算机(电脑)第2节 计算机历史 算盘(机械/手动) 源于中国具体作者不详,发明时间不详.我国第一颗原子弹的很多数据早期…

【论文速递】ACM2022 - 基于嵌入自适应更新和超类表示的增量小样本语义分割

【论文速递】ACM2022 - 基于嵌入自适应更新和超类表示的增量小样本语义分割 【论文原文】&#xff1a;Incremental Few-Shot Semantic Segmentation via Embedding Adaptive-Update and Hyper-class Representation 获取地址&#xff1a;https://arxiv.org/pdf/2207.12964.pd…

靶机测试djinn笔记

靶机地址https://www.vulnhub.com/entry/djinn-1,397/靶机测试信息收集nmap扫描nmap -p- -A 192.168.1.106 -oA dj 通过 nmap 扫描得到21 端口 可以匿名访问22 端口 ssh 但是被过滤了 1337 是一个游戏端口7331 是 python web测试 1337 端口访问端口nc -vv 192.168.0.177 1337这…

智慧工地安全帽智能识别检测 yolov5

智慧工地安全帽智能识别检测通过yolov5opencv深度学习技术&#xff0c;可自动对现场画面检测识别人员有没有戴安全帽。OpenCV基于C实现&#xff0c;同时提供python, Ruby, Matlab等语言的接口。OpenCV-Python是OpenCV的Python API&#xff0c;结合了OpenCV CAPI和Python语言的最…

【Spring(六)】彻底搞懂Spring的依赖注入

文章目录前言依赖注入setter注入构造器注入自动装配集合注入总结前言 在核心容器这一部分bean相关的操作&#xff0c;我们已经学完了&#xff0c;接下来我们就要进入到第二个大的模块&#xff0c;与我们的DI,也就是依赖注入相关知识的学习了&#xff0c;那我们先来学习第一个内…

ChatGPT!我是你的破壁人;比尔·盖茨不看好Web3与元宇宙;FIFA押中4届世界杯冠军;GitHub今日热榜 | ShowMeAI资讯日报

&#x1f440;日报合辑 | &#x1f3a1;AI应用与工具大全 | &#x1f514;公众号资料下载 | &#x1f369;韩信子 &#x1f3a1; 『GPTZero』用 ChatGPT 写论文糊弄老师&#xff1f;已经不灵了~ 语言生成模型的诞生与优化&#xff0c;给教育和学术界带来了不少困扰。继纽约教育…

前端工程化解决方案-Webpack编程

文章目录1. 前端工程化目前主流的前端工程化解决方案2.webpack2.1 主要供能2.2 webpack与webpack-cli的使用2.2.1 初始化项目2.2.2 安装2.2.3 配置2.2.3.1 webpack.config.js2.2.3.2 package.json2.2.3.3 打包构建2.2.3.4 项目中引入 dist/bundle.js2.3 动态部署2.3.1 webpack-…

微服务架构概述

微服务架构概述一、架构演变1.1 单体架构1.2 分布式架构1.3 微服务二、SpringCloud2.1 简介3.2 痛点三、SpringCloud Alibaba3.1 简介3.2 优点3.3 主要组件3.4 版本对应一、架构演变 1.1 单体架构 讲业务的所有功能集中在一个项目中开发&#xff0c;打成一个包部署。 优点&am…

SaaS架构实现理论(四)可伸缩多租户

目录1.伸缩性&#xff08;Scalable&#xff09;的概念2.应用服务器层的水平扩展2.1基于Session复制的水平扩展方式2.2基于Session Sticky的水平扩展方式2.3基于Cache的集中式Session实现水平扩展2.4三种水平扩展方式的比较3.数据库的水平扩展3.1数据库的垂直切分3.2数据库的读写…

ArcGIS基础实验操作100例--实验93插值模型的精度分析

本实验专栏参考自汤国安教授《地理信息系统基础实验操作100例》一书 实验平台&#xff1a;ArcGIS 10.6 实验数据&#xff1a;请访问实验1&#xff08;传送门&#xff09; 空间分析篇--实验93 插值模型的精度分析 目录 一、实验背景 二、实验数据 三、实验步骤 &#xff08;…