【数据结构】散列表(哈希表)

news2024/12/29 9:44:49

文章目录

  • 前言
  • 一、什么是散列表
  • 二、什么是哈希函数
  • 三、下面简单介绍几种哈希函数
  • 四、冲突
    • 处理散列冲突的方法
      • 开放定址法
      • 再散列函数法
      • 公共溢出区法
      • 链地址法
  • 五、代码实现
    • 1.哈希函数
    • 2.链表和哈希表的创建
    • 3.哈希表初始化
    • 3.从哈希表中根据key查找元素
    • 4.哈希表插入元素
    • 5.元素删除
    • 6.哈希表销毁

前言

让我们想一下,若在手机通信录中查找一个人,那我们应该不会从第 1 个人一直找下去,因为这样实在是太慢了。我们其实是这样做的:首先看这个人的名字的首字母是什么,比如姓张,那么我们一定会滑到最后,因为“Z”姓的名字都在最后。

还有在查字典时,要查找一个单词,肯定不会从头翻到尾,而是首先通过这个单词的首字母,找到对应的那一页;再找第 2 个字母、第 3 个字母……这样可以快速跳到那个单词所在的页。

其实这里就用到了散列表的思想。

一、什么是散列表

散列表(hash table),我们平时叫它哈希表或者Hash 表,你肯定经常听到它。

散列表是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。

由定义我们可以知道,散列表用的是数组支持下标访问数据的特性,所以散列表是数组的一种扩展,有数组演化而来。

二、什么是哈希函数

哈希函数就是将键转化为数组索引的过程,这个函数应该易于计算且能够均与分布所有的键。

三、下面简单介绍几种哈希函数

  • 直接寻址法:取关键字或关键字的某个线性函数值为散列地址。
  • 数字分析法:通过对数据的分析,发现数据中冲突较少的部分,并构造散列地址。例如同学们的学号,通常同一届学生的学号,其中前面的部分差别不太大,所以用后面的部分来构造散列地址。
  • 平方取中法:当无法确定关键字里哪几位的分布相对比较均匀时,可以先求出关键字的平方值,然后按需要取平方值的中间几位作为散列地址。这是因为:计算平方之后的中间几位和关键字中的每一位都相关,所以不同的关键字会以较高的概率产生不同的散列地址。
  • 取随机数法:使用一个随机函数,取关键字的随机值作为散列地址,这种方式通常用于关键字长度不同的场合。
  • 除留取余法:取关键字被某个不大于散列表的表长 n 的数 m 除后所得的余数 p 为散列地址。这种方式也可以在用过其他方法后再使用。该函数对 m 的选择很重要,一般取素数或者直接用 n。

以上方法是对数字类型的操作,对字符串类型的数据,可以选择通过相加或者进位转化成数字后,再执行上面的计算方法。

四、冲突

冲突就是,两个不同的关键字,但是通过散列函数得出来的地址是一样的。
key1 ≠ key2,但是f(key1)= f(key2)

同义词
此时的key1 和key2就被称为这个散列函数的同义词

那可不行啊,一件单人间怎么可以住两个人呢?

别担心,这个问题自然已经被神通广大的大佬们解决了。

处理散列冲突的方法

开放定址法

开发定址法就是一旦发生了冲突,就去寻找下一个空的散列地址,只需要散列表足够大,空的散列地址总能找到,并将记录存入

例子:
19 01 23 14 55 68 11 86 37
要存储在表长11的数组中,其中H(key)=key MOD 11

  1. 线性探测法
    公式
f1(key) = (f(key)+d1) MOD m(di=1,2,3,....,m-1)

我们取di等于1

index012345678910
key551141986
23冲突23
68冲突68冲突68
11冲突11冲突11冲突11冲突11冲突11
37冲突37冲突37
最终存储结果55123146811371986
  1. 二次探测法
    增加平方运算的目的是为了不让关键字都聚再某一块区域,我们称这种方法为二次探测法
    公式:
f1(key) = (f(key)+d1) MOD m(di=1^2,-1^2,2^2,-2^2,...,q^2,-q^2,q<=m/2)
index012345678910
key551141986
23冲突f(23)+1
f(68)-1冲突68冲突f(68)+1冲突f(68)+4
11冲突f(11)+1冲突f(11)-1
最终存储结果551231468198611
  1. 随机探测法
    在冲突时,对于位移量di采用随机函数计算得到,我们称之为随机探测法
    公式
f1(key) = (f(key)+d1) MOD m(di是一个随机数列)

具体方法和上面一样
就不多赘述了

再散列函数法

对于我们的散列表来说,我们事先需要准备多个散列函数

f(key)=RHi(key) (i=1,2...,3)

这里的RHi就是不同的散列函数,每当发生冲突时,就换一个散列函数进行计算,总有一个函数可以将冲突解决

公共溢出区法

在原先基础表的基础上再添加一个溢出表
当发生冲突时,就将该数据放到溢出表中
在查找时,对给定值通过散列函数计算出散列地址后,先与基本表的相应位置进行对比,如果相等就查找成功,如果不相等,则到溢出表进行顺序查找

在这里插入图片描述

链地址法

就时用链表将发生冲突的数据链起来,在查找时,只需要遍历链表即可
如下图
在这里插入图片描述

此方法也是我们最长用处理哈希冲突的方法

五、代码实现

1.哈希函数

//哈希函数
int Hash(int key, int TableSize)
{
	return key % TableSize;
}

2.链表和哈希表的创建

#define DEFAULT_SIZE 16
typedef int type;
//结点
typedef struct ListNode
{
	struct ListNode* next;
	int key;  //线索
	type* data; //数据
}ListNode;
//提高可读性
typedef ListNode* List;
typedef ListNode* Element;
//哈希表
typedef struct HashTable
{
	int TableSize;
	List* Thelists;
}HashTable;

3.哈希表初始化

HashTable* InitHash(int TableSize)
{
	int i = 0;
	HashTable* htable = NULL;
	if (TableSize <= 0)
	{
		TableSize = DEFAULT_SIZE;
	}
	htable = (HashTable*)malloc(sizeof(HashTable));
	if (htable == NULL)
	{
		printf("初始化失败\n");
		return NULL;
	}
	//为桶分配内存空间,其为一个指针数组
	htable->Thelists = (List*)malloc(sizeof(List) * TableSize);
	if (htable->Thelists == NULL)
	{
		printf("初始化失败\n");
		free(htable);
		return NULL;
	}
	//为Hash桶对应的指针数组初始化链表结点
	for (i = 0; i < TableSize; i++)
	{
		htable->Thelists[i] = (ListNode*)malloc(sizeof(ListNode));
		if (htable->Thelists[i] == NULL)
		{
			printf("初始化失败\n");
			free(htable->Thelists);
			free(htable);
			return NULL;
		}
	}
}

3.从哈希表中根据key查找元素

Element Find(HashTable* HashTable, int key)
{
	int i = 0;
	List L = NULL;
	Element e = NULL;
	i = Hash(key, HashTable->TableSize);
	L = HashTable->Thelists[i];
	e = L->next;
	while (e != NULL && e->key != key)
		e = e->next;
	return e;
}

4.哈希表插入元素

void Insert(HashTable* HashTable, int key, type* value)
{
	Element e = NULL, temp = NULL;
	List L = NULL;
	e = Find(HashTable, key);
	if (e == NULL)
	{
		temp = (Element)malloc(sizeof(ListNode));
		if (temp == NULL)
		{
			printf("malloc error\n");
			return;
		}
		L = HashTable->Thelists[Hash(key, HashTable->TableSize)];
		temp->data = value;
		temp->key = key;
		L->next = temp;
	}
	else
		printf("the key already exist\n");
}

5.元素删除

void Delete(HashTable* HashTable, int key)
{
	Element e = NULL, last = NULL;
	List L = NULL;
	int i = Hash(key, HashTable->TableSize);
	L = HashTable->Thelists[i];
	last = L;
	e = L->next;
	while (e != NULL && e->key != key)
	{
		last = e;
		e = e->next;
	}
	if (e)
	{
		last->next = e->next;
		free(e); 
	}
	else
	{
		printf("该元素不存在\n");
	}
}

6.哈希表销毁

void Destory(HashTable* HashTable)
{
	int i = 0;
	List L = NULL;
	Element cur = NULL, next = NULL;
	for (i = 0; i < HashTable->TableSize; i++)
	{
		L = HashTable->Thelists[i];
		cur = L->next;
		while (cur->next != NULL)
		{
			next = cur->next;
			free(cur);
			cur = next;
		}
	}

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

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

相关文章

100天精通Python(可视化篇)——第85天:matplotlib绘制不同种类炫酷气泡图参数说明+代码实战(网格、自定义颜色、钟型、交互、打卡、动态气泡图)

文章目录 专栏导读1. 气泡图介绍1&#xff09;介绍2&#xff09;参数说明 2. 普通气泡图3. 网格气泡图4. 自定义气泡图颜色5. 不同颜色气泡图6. 钟型气泡图7. 交互气泡图8. 打卡气泡图9. 动态气泡图 专栏导读 &#x1f525;&#x1f525;本文已收录于《100天精通Python从入门到…

实战打靶集锦-020-Tre

提示&#xff1a;本文记录了博主一次艰难又失败的提权经历 目录 1. 主机发现2. 端口扫描3. 服务枚举4. 服务探查4.1 Apache探查4.1.1 adminer.php4.1.2 /cms/目录4.1.3 /info.php页面4.1.4 nikto扫描4.1.5 dirb扫描4.1.6 登录Adminer 5. 提权5.1 系统信息枚举5.2 定时任务枚举5…

SocketTools 11在所有HTTP客户端组件支持

SocketTools 11在所有HTTP客户端组件支持 在所有HTTP客户端组件中添加了对HTTP/2.0协议的支持。 更新了TLS 1.2(及更高版本)和SSH 2.0的安全选项&#xff0c;以使用Microsoft Windows 11和Windows Server 2022中提供的密码套件。较旧、安全性较低的密码套件已被弃用&#xff0…

21级计组硬件实验三-八位串行加法器验证

问题1&#xff1a; 小明同学正在做【半加器、全加器与八位串行加法器实验】&#xff0c;他画好了电路图&#xff0c;但还有不少困难&#xff0c;现在他求助于你&#xff0c;请你帮他解决。 答&#xff1a;选A 注释&#xff1a;A是与门&#xff0c;B是或门&#xff0c;C是同…

【cmake】cmake 实现交叉编译

在PC上开发时&#xff0c;我们可以直接在PC端编译、链接、运行&#xff0c;但是到了嵌入式环境&#xff0c;由于嵌入式的设备资源&#xff08;CPU、RAM&#xff09;无法和PC端相比&#xff0c;如果项目比较复杂&#xff0c;很难甚至不可能在设备上编译。因此&#xff0c;我们一…

vue diff算法与虚拟dom知识整理(9) 手写patch递归子节点上树,用自己写的patch实现虚拟节点替换

上文 我们做到让一个文字虚拟节点上树 但子节点显然还 没有完成 那这次我们继续 递归的话 我们需要换个思路 我们将 src下的入口文件 index.js代码改成这样 import h from "./snabbdom/h"; import patch from "./snabbdom/patch";const container docum…

Apache Zeppelin系列教程第七篇——运行paragraph的整个流程分析

Zeppelin运行paragraph的整个流程分析 前文分别讲述了&#xff0c;JdbcInterpreter、Interpreter、Zengine的流程&#xff0c;本文来主要串联起来paragraph的整个流程 前端 首先前端部分点运行的时候是通过websocket向后端发送请求的zeppelin-web/src/components/websocket/…

swp协议-1

Swp接口是UICC和CLF&#xff08;非接前端&#xff09;之间的面向比特流&#xff0c;点到点通信的协议。CLF是主设备&#xff08;master&#xff09;&#xff0c;UICC是从设备&#xff08;slave&#xff09;。图SWP数据传输虽然是单线协议&#xff0c;但是是全双工数字传输。 1 …

电商项目之海量操作日志的实现

文章目录 1 问题背景2 前言3 思考4 解决思路5 交互6 工作原理7 伪代码实现7.1 安装并配置Canal Server7.2 Canal客户端拉取MQ消息7.3 Canal数据的转换7.4 定制自己的业务逻辑 1 问题背景 有时候客户做了某些操作却不认账&#xff0c;咱们又拿不出证据&#xff1b;有时候客户将账…

入参校验1

文章目录 一、简介1、快速失败(Fail Fast) 二、单字段类入参校验三、JSON实体类校验1、注解解析2、案例1、简单校验2、分组校验3、嵌套校验4、集合校验5、自定义校验 四、相关1、源码文件2、参考地址 一、简介 1、快速失败(Fail Fast) Spring Validation 默认会校验完所有字段…

GPT-4的免费使用方法分享(续)

GPT-4的免费使用方法分享_我爱OJ的博客-CSDN博客 在这篇博客里&#xff0c;我介绍了一些ChatGPT的一些使用方法&#xff0c;但可能有一定的缺陷&#xff0c;有的需要魔法&#xff0c;所以&#xff0c;今天我就来亲测一下&#xff0c;关于ChatGPT的一些免费使用技巧 目录 镜像…

代码随想录算法训练营第九天|KMP算法

记录一下KMP算法&#xff0c;本文摘录自《代码随想录》和部分b站视频帮你把KMP算法学个通透&#xff01;&#xff08;理论篇&#xff09;_哔哩哔哩_bilibili最浅显易懂的 KMP 算法讲解_哔哩哔哩_bilibiliKMP字符串匹配算法2_哔哩哔哩_bilibili KMP算法 主要应用&#xff1a;字…

牛客小白月赛65

题目链接 牛客小白月赛65 A-牛牛去购物&#xff08;枚举&#xff09;B-牛牛写情书&#xff08;字符串&#xff09;C-牛牛排队伍&#xff08;模拟&#xff09;D-牛牛取石子&#xff08;博弈&#xff09;E-牛牛的构造&#xff08;构造&#xff0c;思维&#xff09; A-牛牛去购物…

怎么免费使用 ChatGpt,实用!

最近发现了一个可以免费、轻松使用 ChatGpt 的方法&#xff0c;随即做个记录&#xff0c;留着备忘&#xff0c;以后想用也能随时找到方法。 但是不保证该方法永远有效&#xff0c;仅当下有限&#xff0c;做个记录罢了。 因为我使用的是 windows 自带的浏览器 Microsoft Edge &a…

Android--刷机与adb

目录 一、Android设备启动流程 二、刷机模式介绍 三、Windows命令行 四、adb介绍与配置 五、常用的adb命令 一、Android设备启动流程 Android就是Linux内核(Kernel)Java虚拟机(JVM) Android设备启动就分为两个阶段&#xff1a; Linux启动 1.启动电源以及系统启动&#…

详解c++STL—容器list

目录 1、list基本概念 1.1、概念描述 1.2、结点的组成 1.3、list的优点 1.4、list的缺点 1.5、总结 2、list构造函数 2.1、功能描述 2.2、函数原型 2.3、示例 3、list赋值和交换 3.1、功能描述 3.2、函数原型 3.3、示例 4、list大小操作 4.1、功能描述 4.2、函…

ChatGPT的前世今生——混沌初开

目录 ChatGPT的前世今生——混沌初开ChatCPT简介ChatCPT是什么&#xff1f;ChatCPT的火爆程度ChatCPT火爆的原因1、功能强大&#xff0c;应用范围广泛2、训练数据量大&#xff0c;模型效果好3、优秀的商业模式 OpenAI公司公司创始团队 总结公众号文章链接参考链接&#xff1a; …

03C++类与对象之运算符重载

文章目录 C类与对象之运算符重载与const成员运算符重载赋值运算符重载运算符重载 日期类的实现与运算符重载赋值运算符重载比较类运算符的重载二元运算符-的重载前置和后置重载 总体实现代码const成员const的好处1.防止程序员犯错2.提高代码的复用性 const 成员与函数重载规则 …

Qt文件系统源码分析—第三篇QDir

深度 本文主要分析Windows平台&#xff0c;Mac、Linux暂不涉及 本文只分析到Win32 API/Windows Com组件/STL库函数层次&#xff0c;再下层代码不做探究 本文QT版本5.15.2 类关系图 QTemporaryFile继承QFile QFile、QSaveFile继承QFileDevice QFileDevice继承QIODevice Q…

由浅入深Netty基础知识IO相关

目录 1 stream vs channel2 IO 模型3 零拷贝3.1 传统 IO 问题3.2 NIO 优化 4 AIO4.1 文件 AIO4.2 守护线程4.3 网络 AIO 1 stream vs channel stream 不会自动缓冲数据&#xff0c;channel 会利用系统提供的发送缓冲区、接收缓冲区&#xff08;更为底层&#xff09;stream 仅支…