数据结构——哈希技术及链地址法

news2025/4/12 22:08:13

目录

一、哈希的定义

二、哈希冲突定义

 三、构造哈希函数的方法

四、四种解决哈希冲突的方法

4.1 开放地址法

4.2 链地址法

4.3 再散列函数法

4.4 公共区溢出法

五、链地址法结构体设计

六、基本操作的实现

6.1 哈希函数

6.2 初始化

6.3 插入值

6.4 删除值

6.5 查找值

6.6 打印

6.7 测试用例 

一、哈希的定义

哈希(Hash)是一种将任意长度的输入数据通过哈希算法转换成固定长度(通常是固定长度的字符串)输出的过程。哈希算法通常会将输入数据映射为一个固定长度的字符串,这个字符串通常称为哈希值或摘要。

也就是说,我们只需要通过某个函数f ,使得存储位置=f(关键字),那么我们可以通过查找关键字不需要比较就可以获得需要记录的存储位置。

哈希函数具有以下特点:

  1. 输入数据相同,输出的哈希值必定相同。
  2. 不同的输入数据,哈希值是独立的,即不会有冲突。
  3. 哈希值的长度是固定的,不会随输入数据的长度变化而变化。
  4. 哈希值是不可逆的,即无法从哈希值还原出原始的输入数据。

哈希既是一种存储方法,也是一种查找方法。

二、哈希冲突定义

两个或多个关键码key1!=key2,但是通过哈希函数的计算,得出的结果却相等,这种现象就是发生了哈希冲突。

例如:f(x)=x %10

例如上述的86和66,计算得出都应该存放在6号下标,冲突了。 

 三、构造哈希函数的方法

构造哈希函数的方法有很多种。其中一种常见的方法是利用数学运算来将输入数据映射到固定大小的哈希值。

以下是一些构造哈希函数的方法:

  1. 直接寻址法:将输入数据直接作为索引值,获取对应的哈希值。

  2. 除留余数法:将输入数据除以一个数,取余数作为哈希值。

  3. 平方取中法:对输入数据进行平方运算,然后取中间几位作为哈希值。

  4. 折叠法:将输入数据分割成固定长度的片段,对每个片段进行加法或异或运算,最终得到哈希值。

  5. 随机哈希函数:使用随机数生成器生成哈希函数,将输入数据与随机数进行运算来得到哈希值。

  6. 加法哈希函数:将输入数据中的每个字符转换成ASCII码,然后求和得到哈希值。

  7. 乘法哈希函数:将输入数据乘以一个常数,取乘积的某几位作为哈希值。

四、四种解决哈希冲突的方法

4.1 开放地址法

         线性探测法:

     但是这种方法会存在一种情况:两个值本来都不是同义词却需要争夺一个地址,这种现象叫做堆积 

     二次探测法:

所以我们想着再探测的时候,尽可能的即向左探测,也向右探测,并且探测的幅度还在呈指数爆炸的趋势增加。这种方法叫做二次探测法

      随机探测法: 

4.2 链地址法

当哈希表用链地址法处理冲突时,每个槽位都存储一个链表或其他数据结构,该链表用于存储哈希值相同的键值对。当需要查找、插入或删除一个键值对时,首先计算对应的哈希值,然后根据哈希值找到对应的槽位,最后在该槽位上的链表中进行操作。

优点:链地址法的优点是容易实现和理解,可以有效地处理哈希冲突,适用于存储大量数据的情况。

缺点:链地址法可能会浪费一定的空间用于存储链表的指针,同时在遍历链表时可能会引起缓存未命中。

4.3 再散列函数法

当发生哈希冲突时,再散列函数法会根据一个特定的规则选择另一个哈希函数,将原始的关键字重新哈希,生成一个新的哈希值。然后,再检查新的哈希值对应的槽位是否为空,如果为空则将数据插入该位置,如果不为空则继续使用再散列函数生成新的哈希值,直到找到一个合适的位置为止。

4.4 公共区溢出法

公共区溢出法与链地址法不同的是,公共区溢出法在哈希表的每个槽位中直接存储键值对,当发生哈希冲突时,会在其他空槽位中寻找可用的位置来存储冲突的键值对。

具体来说,当插入一个键值对时,如果计算得出的哈希值对应的槽位已经被占用,那么就会根据某种探测序列在哈希表中查找下一个可用的空槽位,并将键值对存储在该位置上。具体的探测序列可以是线性探测、二次探测、双重散列等。

    优点:

    1. 公共区溢出法不需要额外的数据结构来存储冲突的键值对,节省了额外的空间开销。
    2. 可以提高数据的局部性,减少缓存未命中的可能性。
    3. 插入、查找和删除操作时,不需要额外的指针操作,节省了内存。

    缺点:

    1. 当哈希表变满时,可能会增加插入操作的复杂度,可能导致性能下降。
    2. 如果探测序列选择不当,可能会导致产生大量的聚集现象,影响查找效率。
    3. 删除操作可能较为繁琐,需要标记删除的键值对。

    五、链地址法结构体设计

    链地址法中有效节点的结构体设计:1.数据域  2.指针域

    //链地址法有效节点的结构体设计
    typedef int ElemType;
    typedef struct List_Node
    {
    	ElemType data;  //数据域
    	struct List_Node* next; //指针域
    }List_Node, *PList_Node; 
    

    链地址法中辅助节点的结构体设计: 1. 数组,有INIT_SIZE个格子,每一个格子存放的是单链表的辅助节点。

    //链地址法辅助节点的结构体设计
    #define INITSIZE 12
    typedef struct List_address
    {
    	struct List_Node arr[INITSIZE];
    }List_address;

    六、基本操作的实现

    6.1 哈希函数

    哈希函数实现,就是计算给定元素的哈希值。其中,ElemType代表元素的类型,INITSIZE代表哈希表的初始大小。

    哈希函数采用了取余运算符%来计算元素的哈希值,具体操作是将给定元素val除以INITSIZE后取余数。取余运算可以将元素的值映射到一个较小的范围内,使得得到的哈希值在哈希表的合法索引范围内。

    代码实现如下:

    //哈希函数
    int Hash(ElemType val)
    {
    	return val % INITSIZE;
    }


    6.2 初始化

    通过for循环,调用单链表中的初始化函数即可完成初始化。

    //初始化
    void Init_List_Address(List_address* pla)
    {
    	for (int i = 0; i < INITSIZE; i++)
    	{
    		InitList(&pla->arr[i]);
    	}
    }


    6.3 插入值

    1. 首先,函数接受一个指向哈希表的指针pla和待插入的元素值val作为参数。

    2. 通过调用之前提到的哈希函数Hash(val)来计算元素val的哈希值,并将其赋值给index变量。

    3. 接着,通过调用malloc()函数动态分配内存,创建一个新的节点pnewnode,用于存储待插入的元素。

    4. 如果内存分配成功(即pnewnode不为NULL),则将元素值val赋给新节点的数据域data

    5. 将新节点的next指针指向哈希表中对应索引位置的链表头节点,以实现在链表头插法的方式将新节点插入到哈希表中。

    6. 最后,返回true表示插入成功。

    代码实现如下:

    //插入值
    bool Insert(List_address* pla, ElemType val)
    {
    	assert(pla != NULL);
    	int index = Hash(val);
    	Node* pnewnode = (Node*)malloc(sizeof(Node));
    	if (NULL == pnewnode)
    		return false;
    	pnewnode->data = val;
    	pnewnode->next = pla->arr[index].next;
    	pla->arr[index].next = pnewnode;
    	return true;
    }
    


    6.4 删除值

    1. 首先,函数接受一个指向哈希表的指针pla和待删除的元素值val作为参数。

    2. 通过调用哈希函数Hash(val)计算元素val的哈希值,并将其赋值给index变量。

    3. 调用Hash_List_Address_Search()函数来查找哈希表中是否存在值为val的节点,将返回的节点赋给指针 q。

    4. 如果节点qNULL,即未找到待删除的元素,则返回false表示删除失败。

    5. 如果找到了值为val的节点q,则进入循环,遍历哈希表中索引为index的链表,找到节点q的前驱节点,即节点p

    6. 在找到节点q的前驱节点p后,将前驱节点的next指针指向节点q的后继节点,实现删除节点q的操作。

    7. 通过调用free(q)释放节点q占用的内存空间,并将指针q设为NULL,避免悬空指针。

    8. 最后,返回true表示删除成功。

    代码实现如下:

    bool Delete(List_address* pla, ElemType val)
    {
    	assert(pla != NULL);
    	int index = Hash(val);
    	Node* q = Hash_List_Address_Search(pla, val);
    	if (q == NULL)
    		return false;
    	//此时代码执行到这,证明val值节点存在在index下标里面的单链表上
    	Node* p = &pla->arr[index];
    	for (; p->next != q; p = p->next);
    	//此时代码执行到这里,证明p和q都就位
    	p->next = q->next;
    	free(q);
    	q = NULL;
    	return true;
    }


    6.5 查找值

    1. 首先,函数接受一个指向哈希表的指针pla和待查找的元素值val作为参数。

    2. 通过调用哈希函数Hash(val)计算元素val的哈希值,并将其赋值给index变量。

    3. 查找哈希表中索引为index的单链表的起始节点,并将其赋给指针p。

    4. 进入循环,遍历哈希表中索引为index的链表,逐个比较节点中存储的数据值是否等于待查找的元素值val。

    5. 如果找到与待查找的元素值相等的节点,则返回该节点的指针p,表示找到了目标节点。

    6. 如果在整个链表中都没有找到与待查找的元素值相等的节点,则循环结束后,返回NULL,表示未找到目标节点。

    代码实现如下:

    struct Node* Hash_List_Address_Search(List_address* pla, ElemType val)
    {
    	assert(pla != NULL);
    	int index = Hash(val);
    	Node* p = pla->arr[index].next;
    	for (; p != NULL; p = p->next)
    	{
    		if (p->data == val)
    		{
    			return p;
    		}
    	}
    	return NULL;
    }


    6.6 打印

    void Show(List_address* pla)
    {
    	for (int i = 0; i < INITSIZE; i++)
    	{
    		printf("第%d行:", i);
    		Node* p = pla->arr[i].next;
    		for (; p != NULL;p = p->next)
    		{
    			printf("%d->", p->data);
    		}
    		printf("\n");
    	}
    }

    6.7 测试用例 

    int main()
    {
    	List_address head;
    	Init_List_Address(&head);
    	Insert(&head, 12);
    	Insert(&head, 67);
    	Insert(&head, 56);
    	Insert(&head, 16);
    	Insert(&head, 25);
    	Insert(&head, 37);
    	Insert(&head, 22);
    	Insert(&head, 29);
    	Insert(&head, 15);
    	Insert(&head, 47);
    	Insert(&head, 48);
    	Insert(&head, 34);
    	Show(&head);
    
    	printf("-----------------------------\n");
    
    	Delete(&head, 25);
    	Delete(&head, 12345);
    	Show(&head);
    	return 0;
    }

    运行结果如下:

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

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

    相关文章

    【Docker】快速部署 Certbot 并为 Nginx 服务器配置 SSL/TLS 证书

    【Docker】快速部署 Certbot 并为 Nginx 服务器配置 SSL/TLS 证书 引言 Certbot 是一个免费的开源工具&#xff0c;用于自动化管理和获取 SSL/TLS 证书&#xff0c;主要用于与 Let’s Encrypt 证书颁发机构交互。 步骤 Nginx 挂载 certbot 文件夹。 docker run -d \--name…

    Redis下载稳定版本5.0.4

    https://www.redis.net.cn/download/ Redis下载 Redis 版本号采用标准惯例:主版本号.副版本号.补丁级别,一个副版本号就标记为一个标准发行版本,例如 1.2,2.0,2.2,2.4,2.6,2.8,奇数的副版本号用来表示非标准版本,例如2.9.x发行版本是Redis 3.0标准版本的非标准发行版本…

    Google Chrome下载受限制的解决方案【方法指南】

    在国内使用网络时&#xff0c;部分用户在尝试访问Google Chrome官网下载谷歌浏览器时&#xff0c;常常遇到网页无法打开或文件下载失败的情况。这种下载受限制的问题多由网络访问政策或DNS解析异常导致。为了正常获取Google Chrome的最新版安装程序&#xff0c;用户需要通过一些…

    《计算机名人堂》专栏介绍:先驱之路

    名人说&#xff1a;路漫漫其修远兮&#xff0c;吾将上下而求索。—— 屈原《离骚》 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 &#x1f31f;引言&#xff1a;先驱之路一、出发点&#xff1a;讲述数字世界的缔…

    MCP工具的配置文件格式是怎么样的?MCP教程平台推荐

    MCP&#xff08;Model Context Protocol&#xff09;配置文件是AI开发中连接MCP服务器的核心文件&#xff0c;采用JSON格式定义服务参数。它广泛应用于Cursor、ChatWise等AI开发工具&#xff0c;帮助开发者快速配置本地或远程MCP服务。本文将深入解析MCP配置文件的结构、获取方…

    网络安全法规与入门指南

    在当今数字化时代&#xff0c;网络安全已成为保障个人隐私、企业利益和国家安全的关键领域。随着网络攻击的日益复杂和频繁&#xff0c;了解和遵守网络安全法规变得尤为重要。本文将深入探讨网络安全相关法规&#xff0c;并为想要进入这一领域的读者提供实用的入门指南。 一、…

    医院访客登记如何做才能更高效?

    在医院工作过的朋友&#xff0c;大概都有过这样的体验&#xff1a;一到探视时间&#xff0c;门诊大厅、病房入口就开始拥堵&#xff0c;尤其是一些管控较严的科室&#xff0c;如ICU、手术区、儿科病房&#xff0c;来访人员必须逐一登记信息。人一多&#xff0c;就容易出错、漏登…

    我的Hexo自动Webhook部署方案

    前言 最近我也是重新开始管理我的博客网站了&#xff0c;之前大概有了半年的时间没有写文章了。也是经过这半年的学习&#xff0c;我了解的知识更多&#xff0c;更广了。 当我开始写 Hexo 博客的时候&#xff0c;首先我得把 Markdown 文件内容拷贝到服务器上&#xff0c;然后写…

    智慧医院室内导航系统架构拆解:技术选型与性能攻坚指南

    本文面向医院信息化团队技术负责人及医疗IoT解决方案开发者&#xff0c;聚焦解决大规模院区导航系统的扩展性、多源数据融合及实时路径规划等技术难点&#xff0c;提供从架构到落地的完整技术路线图。 如需获取智慧医院导航导诊系统解决方案请前往文章最下方获取&#xff0c;如…

    Docker:安装与部署 Nacos 的技术指南

    1、简述 Nacos(Dynamic Naming and Configuration Service)是阿里巴巴开源的一个动态服务发现、配置管理和服务治理的综合解决方案,适用于微服务架构。 Nacos 主要功能: 服务发现与注册:支持 Dubbo、Spring Cloud 等主流微服务框架的服务发现与注册。动态配置管理:支持…

    tailwindcss 4 使用的一些注意点

    目录 一、tailwindcss 4 官网地址变更了 二、自定义颜色的使用方式 三、安装的时候可能的报错 一、tailwindcss 4 官网地址变更了 之前的官网地址是&#xff1a;Tailwind CSS 中文网 现在的官网地址是&#xff1a;Tailwind CSS - Rapidly build modern websites without e…

    stm32工程,拷贝到另一台电脑编译,错误提示头文件找不到cannot open source input file “core_cm4.h”

    提示 cannot open source input file “core_cm4.h” ,找不到 [ core_cm4.h ] 这个头文件 . 于是我在原电脑工程文件里找也没有找到这个头文件 接下来查看原电脑keil的头文件引入配置,发现只引入了工程文件下的头文件, 那么core_cm4.h到底哪里来的? (到现在我也不清楚怎…

    无锡东亭无人机培训机构电话

    无锡东亭无人机培训机构电话&#xff0c;随着科技的迅猛发展&#xff0c;无人机逐渐走入我们的生活和工作领域&#xff0c;成为多种行业中不可或缺的工具。而在其广泛的应用中&#xff0c;如何正确、熟练地操控无人机成为了关键。因此&#xff0c;找到一家专业的无人机培训机构…

    大厂文章阅读

    1.异步任务处理系统&#xff0c;如何解决业务长耗时、高并发难题&#xff1f; 1)任务失败如何处理(CAS失败也可用)&#xff1a;1.指数退避,匹配下游任务执行系统的处理能力。比如收到下游任务执行系统的流控错误&#xff0c;或者感知到任务执行成为瓶颈&#xff0c;需要指数退…

    卷积神经网络 CNN 系列总结(二)---数据预处理、激活函数、梯度、损失函数、优化方法等

    数据预处理 零中心化、归一化 关于数据预处理我们有3个常用的符号,数据矩阵X,假设其尺寸是[N x D](N是数据样本的数量,D是数据的维度)。 均值减法(Mean subtraction)是预处理最常用的形式。它对数据中每个独立特征减去平均值,从几何上可以理解为在每个维度上都将数据…

    速学Android 16新功能:带有进度的通知类型

    前言 在当前已公布的Android 16版本中新增了一系列的功能特性和API&#xff0c;如&#xff1a; 动态壁纸的内容处理&#xff0c;提供新的 content API 预测性返回更新&#xff0c;添加了finishAndRemoveTaskCallback() 和 moveTaskToBackCallback等API 健康数据共享更新&…

    微信小程序开发:微信小程序上线发布与后续维护

    微信小程序上线发布与后续维护研究 摘要 微信小程序作为移动互联网的重要组成部分,其上线发布与后续维护是确保其稳定运行和持续优化的关键环节。本文从研究学者的角度出发,详细探讨了微信小程序的上线发布流程、后续维护策略以及数据分析与用户反馈处理的方法。通过结合实…

    深度学习基础--CNN经典网络之分组卷积与ResNext网络实验探究(pytorch复现)

    &#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 前言 ResNext是分组卷积的开始之作&#xff0c;这里本文将学习ResNext网络&#xff1b;本文复现了ResNext50神经网络&#xff0c;并用其进行了猴痘病分类实验…

    面向对象的需求分析与UML构造块详解

    目录 前言1 面向对象的需求分析概述2 UML构造块概述3 UML事物详解3.1 结构事物&#xff08;Structural Things&#xff09;3.2 行为事物&#xff08;Behavioral Things&#xff09;3.3 分组事物&#xff08;Grouping Things&#xff09;3.4 解释事物&#xff08;Annotational T…

    计算机视觉色彩空间全解析:RGB、HSV与Lab的实战对比

    计算机视觉色彩空间全解析&#xff1a;RGB、HSV与Lab的实战对比 一、前言二、RGB 色彩空间​2.1 RGB 色彩空间原理​2.1.1 基本概念​2.1.2 颜色混合机制​ 2.2 RGB 在计算机视觉中的应用​2.2.1 图像读取与显示​2.2.2 颜色识别​2.2.3 RGB 色彩空间的局限性​ 三、HSV 色彩空…