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

目录

一、哈希的定义

二、哈希冲突定义

 三、构造哈希函数的方法

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

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.mzph.cn/bicheng/76591.shtml

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

    相关文章

    算法思想之前缀和(二)

    欢迎拜访&#xff1a;雾里看山-CSDN博客 本篇主题&#xff1a;算法思想之前缀和(二) 发布时间&#xff1a;2025.4.11 隶属专栏&#xff1a;算法 目录 滑动窗口算法介绍核心思想大致步骤 例题和为 K 的子数组题目链接题目描述算法思路代码实现 和可被 K 整除的子数组题目链接题目…

    开源的7B参数OCR视觉大模型:RolmOCR

    1. 背景介绍 早些时候&#xff0c;Allen Institute for AI 发布了 olmOCR&#xff0c;这是一个基于 Qwen2-VL-7B 视觉语言模型&#xff08;VLM&#xff09;的开源工具&#xff0c;用于处理 PDF 和其他复杂文档的 OCR&#xff08;光学字符识别&#xff09;。开发团队对该工具的…

    移动端六大语言速记:第14部分 - 数据库操作

    移动端六大语言速记:第14部分 - 数据库操作 本文将对比Java、Kotlin、Flutter(Dart)、Python、ArkTS和Swift这六种移动端开发语言在数据库操作方面的特性,帮助开发者理解和掌握各语言的数据库编程能力。 14. 数据库操作 14.1 SQL查询 各语言SQL查询实现方式对比: 特性Ja…

    有哪些反爬机制可能会影响Python爬取视频?如何应对这些机制?

    文章目录 前言常见反爬机制及影响1. IP 封禁2. 验证码3. 请求头验证4. 动态加载5. 加密与混淆6. 行为分析 应对方法1. 应对 IP 封禁2. 应对验证码3. 应对请求头验证4. 应对动态加载5. 应对加密与混淆6. 应对行为分析 前言 在使用 Python 爬取视频时&#xff0c;会遇到多种反爬…

    ESP32开发入门:基于VSCode+PlatformIO环境搭建指南

    前言 ESP32作为一款功能强大的物联网开发芯片&#xff0c;结合PlatformIO这一现代化嵌入式开发平台&#xff0c;可以大幅提升开发效率。本文将详细介绍如何在VSCode中搭建ESP32开发环境&#xff0c;并分享实用开发技巧。 一、环境安装&#xff08;Windows/macOS/Linux&#xf…

    DeepSeek:穿透行业知识壁垒的搜索引擎攻防战

    DeepSeek&#xff1a;穿透行业知识壁垒的搜索引擎攻防战 文 / 产业智能观察组&#xff08;人机协同创作&#xff09; 一、搜索引擎的"认知折叠"危机 2024年Q1数据显示&#xff0c;百度搜索结果前10页中&#xff0c;61.7%的内容存在"伪专业化"现象——看似…

    SQL 外键(Foreign Key)详细讲解

    1. 什么是外键&#xff1f;​​ ​​定义​​&#xff1a;外键是数据库表中的一列&#xff08;或一组列&#xff09;&#xff0c;用于​​建立两个表之间的关联关系​​。外键的值必须匹配另一个表的主键&#xff08;Primary Key&#xff09;或唯一约束&#xff08;Unique Con…

    5G中的DU和CU的作用

    在5G网络架构中&#xff0c;CU&#xff08;Centralized Unit&#xff0c;集中单元&#xff09; 和 DU&#xff08;Distributed Unit&#xff0c;分布单元&#xff09; 是无线接入网&#xff08;RAN&#xff09;的重要组成部分&#xff0c;它们的分工和作用如下&#xff1a; 1.…

    深度解析 n8n:强大的开源工作流自动化平台

    在数字化时代&#xff0c;企业和个人面临着日益复杂的工作流程和多样化的应用工具&#xff0c;如何高效整合这些资源、实现工作流的自动化成为提升效率的关键。n8n 作为一款开源的工作流自动化平台&#xff0c;凭借其强大的功能、广泛的应用集成能力和灵活的部署方式&#xff0…

    ruby超高级语法

    以下是 Ruby 中一些 极度硬核 的语法和底层特性&#xff0c;涉及元编程的深渊、虚拟机原理、语法黑魔法等&#xff0c;适用于追求极限的 Ruby 开发者&#xff1a; 高级语法一 一、语法核弹级操作 1. 动态修改继承链 class A; def foo; "A"; end end class B; def …

    flutter 获取通话记录和通讯录

    Dart SDK version is 3.7.01 dependencies:flutter:sdk: flutterpermission_handler: ^11.0.1 # 权限管理flutter_contacts: ^1.1.92call_log: ^5.0.5cupertino_icons: ^1.0.8dev_dependencies:flutter_test:sdk: flutterflutter_lints: ^5.0.0 2 contact_and_calls_page.da…

    bash脚本手动清空mysql表数据

    文章目录 1、bash脚本手动清空mysql表数据 1、bash脚本手动清空mysql表数据 #!/bin/bash# 配置区域&#xff08;修改此处&#xff09; MYSQL_USER"root" MYSQL_PASSWORD"123456" MYSQL_HOST"localhost" DATABASES("hps-base:base_test_ite…

    Spark Core编程

    一文读懂Spark Core编程核心要点 最近在学习大数据处理框架Spark&#xff0c;今天来给大家分享一下Spark Core编程中非常重要的内容&#xff0c;包括RDD算子、累加器和广播变量&#xff0c;希望能帮助大家更好地理解和掌握Spark编程。先来说说RDD算子&#xff0c;它是Spark编程…

    SDP(一)

    SDP(Session Description Protocol)会话描述协议相关参数 Session Description Protocol Version (v): 0 --说明&#xff1a;SDP当前版本号 Owner/Creator, Session Id (o): - 20045 20045 IN IP4 192.168.0.0 --说明&#xff1a;发起者/创建者 会话ID&#xff0c;那么该I…

    HarmonyOS:组件布局保存至相册

    一&#xff0c;需求背景 有这样一个需求&#xff0c;将页面上的某个自定义组件以图片的形式保存至相册。 二&#xff0c;需求拆解 根据需求分析&#xff0c;可将需求拆解成两步&#xff1a; 1&#xff0c;将组件转换成图片资源&#xff1b; 2&#xff0c;将图片保存到相册…

    算法中的数论基础

    算法中的数论基础 本篇文章适用于算法考试或比赛之前的临场复习记忆&#xff0c;没有复杂公式推理&#xff0c;基本上是知识点以及函数模版&#xff0c;涵盖取模操作、位运算的小技巧、组合数、概率期望、进制转换、最大公约数、最小公倍数、唯一分解定理、素数、快速幂等知识…

    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标准版本的非标准发行版本…

    ‌UniApp 安卓打包完整步骤(小白向)

    ‌ ‌一、环境准备‌ ‌安装 HBuilderX‌ 下载最新版 HBuilderX 并安装&#xff08;官方 IDE&#xff0c;支持一键打包&#xff09;‌16确保已安装 Node.js&#xff08;用于依赖管理&#xff09;‌26 ‌配置 Android 开发环境‌ 安装 ‌Java JDK 17‌&#xff08;建议选择稳定…

    【Springboot知识】Springboot配置加载机制深入解读

    文章目录 配置加载概述**Spring Boot 配置加载机制详解****一、配置加载顺序&#xff08;优先级由低到高&#xff09;****二、关键配置机制说明****1. Profile 机制****2. 外部化配置****3. 配置属性绑定到 Bean****4. 动态覆盖配置** **三、配置加载流程图****2. 配置导入&…

    AI图像生成

    要通过代码实现AI图像生成&#xff0c;可以使用深度学习框架如TensorFlow、PyTorch或GANs等技术。下面是一个简单的示例代码&#xff0c;演示如何使用GANs生成手写数字图像&#xff1a; import torch import torchvision import torchvision.transforms as transforms import …