搜索结构之哈希

哈希表是一种在O(1)时间内查找元素的数据结构,通过哈希函数实现关键码与存储位置的对应。文章介绍了哈希冲突及其解决方法,包括优化哈希函数、闭散列(开放地址法,如线性探测和二次探测)和开散列(链地址法)。文章讨论了负载因子在决定哈希表性能中的作用,并提供了相关代码示例。
摘要由CSDN通过智能技术生成

哈希表:哈希表是一种可以在O(1)时间内查找到某个元素的数据结构,它是用数组实现的,这依赖于内存的随机访问特性。那哈希表到底是什么样呢?哈希表是通过哈希函数(hashfunc)使元素的存储位置与关键码一一对应的关系,所以在进行元素查找的时候可以通过该函数快速的找到该元素。若结构中存在关键字和key相等的元素,则必然存储在f(k)的位置上。由此,不需要进行比较便可以直接取得所查记录。这个对应函数就被称为散列函数(哈希函数),依照这个思想所建立出来的表就被称为散列表(哈希表)。

常见的哈希函数有很多种,在这里我先用除留余数法简单的画一下哈希表是怎样存储元素的。


但是有没有发现一个问题,如果我们想再存入一个值为16的元素,应该把它存在哪里,这就是哈希冲突问题;所谓哈希冲突就是对于两个不同的数据元素m和n,存在HashFunc(m)==HashFunc(n);即不同的关键字计算出相同的哈希地址,这种现象称为哈希冲突或者哈希碰撞,把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”。

那我们应该怎样去很好的解决掉哈希冲突问题呢?

一般来说有三种解决方式:

第一种:引起哈希冲突的一个很可能的因素就是哈希函数设计的不够合理,哈希函数设计时应遵从下面几条原则:

1.哈希函数的定义域必须包含需要存储的所有关键码,即如果哈希表中有m个地址时,其值域必须在0~m-1之间。

2.哈希函数计算出来的哈希地址应该均匀的分布在整个哈希表中。

3.哈希函数的设计应该进可能的简单一点。

常见的哈希函数可自行百度。

第二种:闭散列

闭散列也叫开放地址法,当发生哈希冲突时,如果哈希表未被装满,说明哈希表中还有位置,可以把key放在下一个空位中去,那么如何去寻找下一个空余位置呢?

线性探测:举个例子:


根据闭散列线性的处理哈希冲突的方法,我们不难发现,不能随便删除哈希表中的元素,如果直接删除会影响其他元素的搜索。假设我们在上面例子中的哈希表中删除了元素32,当我们再想去查找元素12的时候,我们会首先去位置2的地方去找,此时我们发现位置2是空着的,我们就会认为12不存在于哈希表中,我们肯定就不再去向后进行查找了。(只有当位置2的不为空的时候,我们才会认为有可能发生了哈希冲突,才会去向后继续查找)。

采用线性探测的方法非常简单,但是当大量的哈希冲突聚集在一起,容易产生“数据堆积”,不同的关键码占据了可利用的空位置,使得寻找某一个关键字可能需要进行多次的比较,降低了搜索效率。那么如何缓解这种问题呢?我们引入了散列表的负载因子a=插入表中的元素个数/散列表的长度;a是散列表装满程度的标志因子,a越大,表明表中的元素越多,产生冲突的可能性就越大;反之,a越小,表明插入表中的元素就越少,产生哈希冲突的可能性就越小,实际上,散列表的平均查找长度是负载因子a的函数,只是不同处理冲突的方法有不同的函数。对于开放定址法,a非常重要,一般控制在0.7-0.8以下,当插入表中的元素个数=a*散列表长度时,我们就认为该哈希表已经满了,不可以再进行插入元素了。a超过0.8,查表时的cpu缓存命不中的概率呈指数上升趋势。

二次探测:发生哈希冲突时,二次探测法在表中查找下一个空位置的计算公式为: Hi=(H0+i^2)%m;Hi=(H0-i^2)%m;i=1,2,3...

m为哈希表的大小。


在上面的例子中,哈希冲突有三个元素:25,35和55,当插入5的时候直接存在了位置5处,插入35的时候发现位置5被占用了,所以就不能根据公式Hi=(H0+i^2)%m=(5+1)%10=6;尝试去往位置6出插入,发现位置6也被占用了,于是根据公式Hi=(H0-i^2)%m=(5-1)%10=4;尝试往位置4进行插入,位置4为空,所以将35插在了位置4处。接下来插入最后一个元素55时发现4和6的位置都被占用了,于是根据公式Hi=(H0+i^2)%m=(5+2^2)%10=9(此时i=2);尝试向位置9进行插入,并插入成功。

研究表明:当表的程度为质数且表的负载因子不超过0.5时,新的元素一定能够插入,而且任意一个位置都不会被探查两次,因此表中只要有一半的空位置,就不存在表满的问题。在搜索时可以不考虑表装满的情况,但在插入元素的时候必须保证负载因子a不超过0.5,如果超出必须对哈希表进行增容。

用闭散列实现哈希结构的代码如下:

//hash_table.h
#pragma once 
#include<stddef.h>
#include<Windows.h>
#include<stdio.h>
#define Header printf("=============%s===============\n",__FUNCTION__);
#define HashMaxSize 1000 

typedef enum Stat { 
 Empty, 
 Valid, 
 Invalid // 当前元素被删除了 
} Stat; 

typedef int KeyType; 
typedef int ValType; 

typedef size_t (*Hashfunc)(KeyType); 

typedef struct HashElem { 
 KeyType key; 
 ValType value; 
 Stat stat; // 引入一个 stat 标记来作为是否有效的标记 
} HashElem; 

typedef str
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值