数据结构(哈希表)

背景

在对数据的日常处理中,查找是一项基本操作。通常,查找算法都是基于对比的,比如在一条链表中有n个节点,要找到其中的某个节点,最基本的思路就是从头到尾依次遍历每个节点,依次对比每个节点是否是想要的节点,这样的查找方式,称为顺序查找。很显然,顺序查找并不会给查找效率带来任何惊喜,其时间复杂度是提高查找效率的办法有很多,比如可以将这些数据按照二叉搜索树的逻辑结构组织起来,那么从根部开始查找某节点的时间复杂度就变成又或者使用顺序存储并将节点排序,那么每次查找可以从中间开始,进行折半查找,时间复杂度也是不管是顺序查找,还是改良后的BST、折半算法,查找一个节点都需要花一定的时间,之所以要花时间是因为存储节点的时候,节点的位置与节点的字段之间没有对应关系,因此我们需要一个个比对每一个节点,上述算法的差异只是改变了比对的规则,使得效率提高,但仍然是一个一个比对的过程。如果查找节点不需要比对,那就可以节省大量的时间。

哈希表

为了避免节点比对,我们可以在存储节点的时候,让节点的位置和节点本身做一个映射关系,这样一来就可以直接根据节点本身的特征值计算得到节点的位置了,注意:此时节点的位置不是“找”出来的,而是计算出来的。

这种存储数据的方式,被称为哈希表(Hash Table),也被称为散列表。时间复杂度是O(1),即查找任何一个数据理论上不需要时间,直接给出数据所在的位置。

基本概念

哈希表的思路简单易懂,将相关的概念陈述如下:

  • 键(Key):即用来作为节点特征的字段。比如学生的姓名、分数、学号等。

  • 值(Value):节点存储的位置,也被称为哈希地址。

  • 冲突(Conflict):当不同的键映射到相同的值时,称为冲突

  • 哈希函数(Hash Function):将键转换为值的映射关系。

哈希存储就是将键映射为哈希地址,存入右侧的某个空位中。右侧实际上是一个数组,所谓的哈希地址一般指的是数组的下标,哈希表一般指的是该数组。

哈希表主要就是解决两件事情:

  1. 确定一个哈希函数
  2. 解决可能会出现的冲突问题

哈希函数

将节点某字段(即键)转换为哈希地址(值)的过程,就是哈希映射。举个例子,假设要将班级里的学生用哈希表的方式来存储,将姓名作为键,可以有如下哈希映射:

  • 将姓名笔画数,作为节点的哈希地址。
    在这里插入图片描述

从上面的例子可以看到,以笔画数作为映射规则是很不理想的,因为大多数人的姓名笔画数都集中在10-20之间,这不利于将各个元素均匀地分布在哈希表中,并且这种算法很容易有冲突。

哈希函数的选取没有一定之规,但一个大的原则是:尽量充分地使用键的信息,尽量使得值均匀分布。符合这个大原则的其中一种哈希函数,称为除留余数法,即:将键对不大于哈希表长度的最大质数求余,将其结果作为哈希地址。

以上面学生为例,假设班级中学生人数在50人左右,将哈希表数组的长度定为50,那么哈希函数可以是:
在这里插入图片描述

此处,47是不大于50的最大的质数,之所以不能大于50,是因为哈希地址最终是数组的下标,如果比数组的长度还大的话就可能会越界。选取质数则有利于值域分布更加均匀。另外,为了让数据分布更加均匀,可以使用姓名拼音的ASCII码之和来作为键。

采取这种除留余数法获得哈希地址后,映射关系变成:

在这里插入图片描述

可见,经过对哈希函数的改良,使得哈希地址分布更加均匀了,冲突概率也降低了。但从另一方面讲,冲突就像物理实验中的误差,可以被降低,但很多时候无法根除,比如上述例子,假如现在入学一位名字为马化腾的学生,那么将会出现:

在这里插入图片描述

此时,马化腾跟张三虽然姓名信息毫不相干,但是计算出来的哈希地址却是冲突的。如何解决冲突?这是哈希表的第二项重要工作。

解决冲突

1. 开放地址法
解决冲突跟选取哈希函数一样,是可以很灵活的。最简单的想法是:既然某个哈希地址已经有别的数据了,那就换一个位置。比如将数据挪到已冲突的位置的旁边,如果旁边还是冲突那么再试试旁边,这就是所谓的开发地址法解决冲突。

这种看似简单的做法,有很多弊端:

  1. 必须保证哈希表的总大小要大于数据节点数目,否则如果数据填满了整张哈希表,那么除非扩充哈希存储数组,否则不管怎么调整位置,都不可能找到空余的地方。
  2. 多个哈希值冲突的数据节点会在冲突点附近形成“堆积”,每个形成冲突的节点都要将前面冲突所走过的路线再走一遍。
  3. 由于节点所处的真实位置与其从哈希函数计算出来的理论位置可能不一致(被冲突就不一致了),这就导致一个位置的状态不是两种,而必须是三种:有节点、无节点、之前有现在无节点。

关于上述第3个弊端,可以用如下例子加以解释:

  • 张三入学时,根据哈希函数计算被安排到了43号桌,然后麻花藤入学时计算出来的哈希地址也是43号,于是小麻同学只能乖乖地坐在小张的旁边,44号桌。

  • 然后,小张退学了。

  • 然后,我们要查找小麻同学,根据哈希函数,计算出来的哈希地址是43,此时43号桌的状态如果是 没人 的话,那么就会误判以为班级里面没有小麻这位同学,于是产生了错误。

  • 解决这个谬误的办法,要将这样的43号做标记为 之前有人但现在无人 的状态,这样才能顺着解决冲突的办法挨个找去,最终才能找到小麻同学。

2. 链地址法
为了解决开放地址法的弊端,可以在冲突点处设置一条链表,让所有哈希地址冲突的节点链起来,这样就既无需担心节点数量超过哈希表大小,也无需设置节点的第三种状态。

假设有如下数据节点:

23,34,14,38,46,16,68,15,07,31,2623,34,14,38,46,16,68,15,07,31,26

假设按照如下除留余数法得到它们的哈希地址:

H(key)=key%13

练习实例

编写一个程序,产生若干长度与内容都随机的字符串,将字符串存入哈希表中,并在程序中提供查表算法演示。

解析:
使用除留余数法计算每个字符串的哈希地址,使用链地址法解决冲突问题。

#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <time.h>#define SIZE 20typedef int datatype;struct node
{datatype data;struct node *next;
};typedef struct
{unsigned long table_size;struct node **table_entry;}hash_table;void show(hash_table *ht, unsigned long pos, datatype data);hash_table *init_ht(unsigned long size)
{hash_table *ht = malloc(sizeof(hash_table));ht->table_size = size;ht->table_entry = calloc(size, sizeof(struct node *));return ht;
}void hash_add(datatype data, hash_table *ht)
{// 使用保留除数法,获得哈希地址(即数组的下标值)unsigned long hash_addr = data % (SIZE-1);struct node *new = malloc(sizeof(struct node));new->data = data;new->next = NULL;show(ht, hash_addr, data);printf("=================================\n");// 1:该哈希地址可用,直接将新节点放进去if(ht->table_entry[hash_addr] == NULL){ht->table_entry[hash_addr] = new;}// 2:该哈希地址不可用,将新节点链到冲突链表的末尾else{struct node *p = ht->table_entry[hash_addr];while(p->next != NULL){p = p->next;}p->next = new;}
}void show(hash_table *ht, unsigned long pos, datatype data)
{struct node *p;int i;for(i=0; i<ht->table_size; i++){p = ht->table_entry[i];printf("table_entry[%d]: ", i);if(p != NULL){struct node *q = p;while(q != NULL){printf("%d\t", q->data);q = q->next;}}if(pos == i){printf("\t <-- %d\n", data);}else{printf("\n");}}
}int main(void)
{hash_table *ht = init_ht(SIZE);srand(time(NULL));int i;for(i=0; i<10; i++){hash_add(rand()%1000, ht);sleep(1);}show(ht, -1, -1);return 0;
}

该代码实现了一个使用哈希表解决冲突的散列表。

首先定义了一个结构体node,表示链表中的节点,包含一个整型数据data和指向下一个节点的指针next。

然后定义了一个结构体hash_table,表示哈希表,包含一个表的大小table_size和指向存储链表的数组的指针table_entry。

接着定义了函数init_ht,用于初始化哈希表,根据给定的大小创建哈希表并返回。

接下来是函数hash_add,用于向哈希表中添加元素。首先根据数据的哈希值计算哈希地址,然后创建一个新的节点,将数据保存在节点中。如果对应的哈希地址没有冲突,直接将新节点放入哈希表中;如果有冲突,将新节点加入到冲突链表的末尾。

最后是函数show,用于显示哈希表的内容。遍历整个哈希表,打印每个位置上的链表内容。如果指定了位置pos和数据data,则在对应位置显示指示箭头。

在main函数中,首先调用init_ht函数初始化哈希表。然后使用rand函数生成随机数,调用hash_add函数将随机数添加到哈希表中,并通过sleep函数暂停1秒。最后调用show函数显示哈希表的内容。

整个代码实现了使用哈希表解决冲突的散列表。

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

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

相关文章

【每日学点鸿蒙知识】模拟器开启网络、长时任务、兼容性测试支持、丢帧定位、SO中访问rawfile等

1、模拟器如何开启网络&#xff1f; 模拟器使用的是电脑本身的网络&#xff0c;不通过代理即可访问网络。 2、创建子window后&#xff0c;锁屏很短时间内&#xff0c;应用会被杀死&#xff1f; 没开长时任务&#xff0c;锁屏和退后台保活要开长时任务。 应用退至后台后&…

如何解决Eigen和CUDA版本不匹配引起的错误math_functions.hpp: No such file or directory

Apollo9针对RTX40的docker环境里的Eigen库版本是3.3.4&#xff0c;CUDA是11.8: 编译我们自己封装模型的某些component代码时没问题&#xff0c;编译一个封装occ模型的component代码时始终报错: In file included from /usr/include/eigen3/Eigen/Geometry:11:0, …

Mac连接云服务器工具推荐

文章目录 前言步骤1. 下载2. 安装3. 常用插件安装4. 连接ssh测试5. 连接sftp测试注意&#xff1a;ssh和sftp的区别注意&#xff1a;不同文件传输的区别解决SSL自动退出 前言 Royal TSX是什么&#xff1a; Royal TSX 是一款跨平台的远程桌面和连接管理工具&#xff0c;专为 mac…

python修改ppt中的文字部分及插入图片

批量修改ppt中的某个模块&#xff0c;或者批量制作奖状等场景会用到&#xff1b; import os import pandas as pd from pptx import Presentation from pptx.util import Inchesfilepath/Users/kangyongqing/Documents/kangyq/202303/分析模版/批量制作/file1时段预警_副本.pp…

从0到机器视觉工程师(一):机器视觉工业相机总结

目录 相机的作用 工业相机 工业相机的优点 工业相机的种类 工业相机知名品牌 光源与打光 打光方式 亮暗场照明 亮暗场照明的应用 亮暗场照明的区别 前向光漫射照明 背光照明 背光照明的原理 背光照明的应用 同轴光照明 同轴光照明的应用 总结 相机的作用 相机…

gesp(C++一级)(7)洛谷:B3863:[GESP202309 一级] 小明的幸运数

gesp(C一级)&#xff08;7&#xff09;洛谷&#xff1a;B3863&#xff1a;[GESP202309 一级] 小明的幸运数 题目描述 所有个位数为 k k k 的正整数&#xff0c;以及所有 k k k 的倍数&#xff0c;都被小明称为“ k k k 幸运数”。小明想知道正整数 L L L 和 R R R 之间&a…

风力涡轮机缺陷检测数据集,86.6%准确识别率,11921张图片,支持yolo,PASICAL VOC XML,COCO JSON格式的标注

风力涡轮机缺陷检测数据集&#xff0c;86.6&#xff05;准确识别率&#xff0c;11921张图片&#xff0c;支持yolo&#xff0c;PASICAL VOC XML&#xff0c;COCO JSON格式的标注 数据集下载 yolov11&#xff1a; https://download.csdn.net/download/pbymw8iwm/90206849 yolov…

力扣-数据结构-8【算法学习day.79】

前言 ###我做这类文章一个重要的目的还是给正在学习的大家提供方向&#xff08;例如想要掌握基础用法&#xff0c;该刷哪些题&#xff1f;建议灵神的题单和代码随想录&#xff09;和记录自己的学习过程&#xff0c;我的解析也不会做的非常详细&#xff0c;只会提供思路和一些关…

FreeRTOS的内存管理(选择heap4.c文件的理由)

目录 1. 了解FreeRTOS内存管理 2. 了解内存碎片 3.了解各个heap.c的内存分配方法 1.heap1.c 2.heap2.c 3.heap3.c 4.heap4.c 5.heap5.c 总结&#xff1a; 内存管理是一个系统基本组成部分&#xff0c;FreeRTOS 中大量使用到了内存管理&#xff0c;比如创建任务、信号量…

WPF中的Microsoft XAML Behaviors包功能详解

什么是XAML Behaviors(行为) XAML Behaviors 提供了一种简单易用的方法&#xff0c;能以最少的代码为 Windows UWP/WPF 应用程序添加常用和可重复使用的交互性。 但是Microsoft XAML Behaviors包除了提供常用的XAML Behaviors之外&#xff0c;还提供了一些Trigger&#xff08…

一文学习SpringBoot

一、SpringBoot介绍 (一)SpringBoot简介 Spring Boot 是由 Pivotal 团队提供的一个用于简化 Spring 应用初始搭建以及开发过程的框架。它基于 Spring 框架&#xff0c;旨在通过减少配置和简化开发流程来加速应用的开发和部署。Spring Boot 提供了嵌入式的 Tomcat、Jetty 或 Un…

本地小主机安装HomeAssistant开源智能家居平台打造个人AI管家

文章目录 前言1. 添加镜像源2. 部署HomeAssistant3. HA系统初始化配置4. HA系统添加智能设备4.1 添加已发现的设备4.2 添加HACS插件安装设备 5. 安装cpolar内网穿透5.1 配置HA公网地址 6. 配置固定公网地址 前言 大家好&#xff01;今天我要向大家展示如何将一台迷你的香橙派Z…

streamlit、shiny、gradio、fastapi四个web APP平台体验

streamlit、shiny、gradio、fastapi四个web APP平台体验 经常被问的问题就是&#xff1a;web APP平台哪个好&#xff1f;该用哪个&#xff1f;刚开始只有用streamlit和shiny&#xff0c;最近体验了一下gradio和fastapi&#xff0c;今天根据自己的体会尝试着回答一下。 使用R语…

Presto-简单了解-230403

presto是什么了解一下&#xff1a; 秒级查询引擎&#xff08;不做存储&#xff09;&#xff0c;GB-PB级不依赖于yarn&#xff0c;有自己的资源管理和执行计划支持多种数据源&#xff1a;hive、redis、kafka presto架构 presto优缺点 presto优点 内存到内存的传输&#xff0…

VScode 格式化代码空格记录

点击 -> “文件” -> “首选项" -> “设置” -> 按下图操作&#xff1a; 怎么格式化代码空格&#xff0c;先看下&#xff1a; 保存代码后&#xff0c;这代码自动格式化发&#xff0c;如下图&#xff1a; 你可以试试看就即可

HTML5 开关(Toggle Switch)详细讲解

HTML5 开关&#xff08;Toggle Switch&#xff09;详细讲解 1. 任务概述 开关&#xff08;Toggle Switch&#xff09;是一种用于表示二元状态&#xff08;如开/关&#xff09;的用户界面控件。用户可以通过点击开关来切换状态&#xff0c;常见于设置选项、开关功能等场景。 2…

Python中PDF转Word的技术

Python PDF转Word技术概述 在日常办公和数据处理中&#xff0c;经常需要将PDF文档转换为Word文档&#xff0c;以便进行编辑、修改或格式调整。Python作为一种强大的编程语言&#xff0c;提供了多种库和工具来实现这一功能。以下是对Python中PDF转Word技术的详细介绍。 一、技…

RabbitMQ中的异步Confirm模式:提升消息可靠性的利器

在现代分布式系统中&#xff0c;消息队列&#xff08;Message Queue&#xff09;扮演着至关重要的角色&#xff0c;它能够解耦系统组件、提高系统的可扩展性和可靠性。RabbitMQ作为一款广泛使用的消息队列中间件&#xff0c;提供了多种机制来确保消息的可靠传递。其中&#xff…

【深度学习】多目标融合算法—样本Loss提权

目录 一、引言 二、样本Loss提权 2.1 技术原理 2.2 技术优缺点 三、总结 一、引言 在朴素的深度学习ctr预估模型中&#xff08;如DNN&#xff09;&#xff0c;通常以一个行为为预估目标&#xff0c;比如通过ctr预估点击率。但实际推荐系统业务场景中&#xff0c;更多是多…

如何在谷歌浏览器中创建安全的密码

在数字化时代&#xff0c;网络安全变得日益重要。谷歌浏览器提供了多种工具和功能帮助用户创建和管理强密码&#xff0c;确保在线账户的安全。本文将简要介绍几种方法&#xff0c;帮助您在谷歌浏览器中创建和管理安全密码。 一、启用自动填充功能 确认密码保存功能已开启&…