散列表/哈希表(C语言简单实现)

本文介绍了散列表的定义、查找步骤、散列函数的构造方法和处理冲突的策略,包括开放定址法、再散列函数法、链地址法等。通过实例展示了C语言中散列表的实现,强调了散列查找的高效性及其对查找性能的影响,以及如何通过选择合适的填装因子来优化查找效率。
摘要由CSDN通过智能技术生成

散列表/哈希表(C语言简单实现)

本文参考自《大话数据结构》

定义

通过某个函数f计算出记录的存储位置,而不需要通过比较,这就是一种新的存储技术——散列技术。

散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f(key) 。在查找时,根据这个确定的对应关系找到给定值key的映射f(key),若查找集合中存在这个记录,则必定在f(key)的位置上。

我们把这种对应关系f称为散列函数,又称为哈希函数。按照这个思路,采用散列技术将记录存储在一块连续的存储空间中,这块连续存储空间称为散列表或哈希表

散列表查找步骤

  1. 在存储时,通过散列函数计算记录的散列地址,并按此散列地址存储该记录;
  2. 当查找记录时,我们通过同样的散列函数计算记录的散列地址,按此散列地址访问该记录。

散列技术既是一种存储方法,也是一种查找方法 。但它与线性表、树、图等结构不同,前面几种结构,数据元素之间都存在某种逻辑关系,可以用连线图示表示出来,但是散列技术的记录之间不存在什么逻辑关系,它只与关键字有关联。因此,散列主要是面相查找的存储结构。

散列技术最适合的求解问题是查找与给定值相等的记录

散列表不适合范围查找,还有一个关键字对应多个记录的情况

散列函数的构造方法

直接定址法

取关键字的某个线性函数值为散列地址,即f(key) = a*key+b(a、b为常数)

数字分析法

使用关键字的一部分来计算散列存储位置的方法,比如操作手机号码的后几位。如果事先知道关键字的分布且关键字的若干位分布较均匀,就可以考虑用这个方法

平方取中法

比如关键字1234,平方就是1522756,取中间的3位就是227。适用不知道关键字的分布,而位数又不是很大的情况

折叠法

折叠法师将关键字从左到右分割成位数相等的几部分(最后一部分位数不够时可以短些),然后将这几部分叠加求和,并按散列表表长,取后几位作为散列地址。有时可能还不够保证分布均匀,不妨从一端向另一端来回折叠后对齐想加。折叠法事先不需要知道关键字的分布,适合关键字位数较多的情况

除留余数法

这是最常用的构造散列函数的方法。对于散列表长为m的散列函数公式为f(key)=key mod p(p<=m),mod是取模(求余数)。事实上,这方法不仅可以对关键字直接取模,也可以在折叠,平方去中后再取模。本方法的关键就在于要选择合适的p。

若散列表表长为m,通常p为小于或等于表长(最好接近m)的最小质数或不包含小于20质因子的合数

随机数法

选择一个随机数,取关键字的随机函数值为它的散列地址。也就是f(key)=random(key)。这里random是随机函数。当关键字的长度不等时,采用这个方法构造散列函数是比较合适的。

对于字符串如何处理?字符串或者中文字符,可以转换成ASCII码或者Unicode码等。

使用不同的散列函数考虑的因素:

  1. 计算散列地址所需的时间
  2. 关键字的长度
  3. 散列表的大小
  4. 关键字的分布情况
  5. 记录查找的频率

处理散列冲突的方法

开放定址法

开放定址法就是一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入。
公式fi(key) = (f(key)+di) mod m (di=1,2,3,...,m-1)

这种解决冲突的开放定址法称为线性探测法。这种方法有一个问题,就是会有本来不是同义词却需要争夺一个地址的情况,我们称这种现象为堆积

改进:增加平方运算的目的是为了不让关键字都聚集在某一块区域,我们称这种方法为二次探测法fi(key)=(f(key)+di) MOD m (di=1^2,-1^2,2^2,-2^2,...,q^2,-q^2,q<=m/2)

还有一种方法,在冲突时,对于位移量di采用随机函数计算得到,我们称之为随机探测法 。这里的随机其实是伪随机数,即i我们设置随机种子相同,则不断调用随机函数可以生成不会重复的数列,在查找的时候,使用同样的随机种子,它每次得到的数列是相同的,相同的di,所以查找的时候可以得到相同的散列地址。
fi(key) = (f(key)+di) MOD m (di是一个随机数列),总之,只要散列表未填满,总能找到不发生冲突的地址。

再散列函数法

可以事先准备多个散列函数fi(key) = RHi(key) (i=1,2,...,k),当发生冲突了,就换一个散列函数计算,这种方法能够使得关键字不产生聚集,但是也会相应地增加运算时间。

链地址法

链地址法师将关键字为同义词的记录存储在一个单链表中,我们称这种表为同义词子表,在散列表中只存储所有同义词子表的头指针。这样做的优点是不会找不到地方来存储冲突的同义词,但是缺点是查找时需要遍历单链表,性能上会损耗

公共溢出区法

公共溢出区结构和基本表一样,就是冲突的数据存储在溢出区,查找的时候先在基本表比对,如果不是,则到溢出区顺序查找。对于冲突少的数据来说,查找性能还是很高的。

散列表查找实现

散列数据结构

#define SUCCESS 1
#define UNSUCCESS 0
#define HASHSIZE 12	//定义散列表长为数组的长度
#define NULLKEY -32768
typedef struct{
   
	int *elem;	//数据元素存储基址,动态分配数组
	int count;	//当前数据元素个数
}HashTable;
int m = 0;	//散列表表长,全局变量

初始化散列表

Status InitHashTable(HashTable *H)
  • 15
    点赞
  • 61
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值