数据结构随笔——散列表(哈希表)

一、散列表

概念

散列技术是在记录的存储位置和它的关键字之间建立-一个确定的对应关系f, 使得每个关键字key 对应一个存储位置f (key)。

通过某个函数使得,存储位置=f(关键字)

这里我们把这种对应关系f称为散列函数,又称为哈希(Hash) 函数。按这个思想,采用散列技术将记录存储在一块连续的存储空间中, 这块连续存储空间称为散列表或哈希表(Hash table)。那么关键字对应的记录存储位置我们称为散列地址。

查找过程

  1. 在存储时,通过散列函数计算记录的散列地址,并按此散列地址存储该记录。

  2. 当查找记录时,我们通过同样的散列函数计算记录的散列地址,按此散列地址访问该记录。由于存取用的是同一个散列函数,因此结果当然也是相同的。

特点

散列函数既是存储方法也是查找方法。其不适合用来范围查找,最适合的求解问题是查找与给定值相等的记录。

另一个问题是冲突。在理想的情况下,每一个关键字,通过散列函数计算出来的地址都是不一样的,可现实中,我们会碰到两个关键字key1≠key2,但是却有f (key1) =f (key2),这种现象我们称为冲突(ollision), 并把key和key2称为这个散列函数的同义词(synonym)。

二、散列函数的构造方法

两个原则 :计算简单,散列地址分布均匀。

直接定址法

我们可以取一个线性函数,f(key)=a*key+b (a,b为常数)

例如:统计80后的人口数,我们可以用出生年份关键字减去1980

在这里插入图片描述

这样的散列函数优点就是简单、均匀,也不会产生冲突,但问题是这需要事先知道关键字的分布情况,适合查找表较小且连续的情况。

数字分析法

使用关键字的一部分,来计算散列存储位置。

例如:某员工登记表
在这里插入图片描述

数字分析法通常适合处理关键字位数比较大的情况,如果事先知道关键字的分布且关键字的若干位分布较均匀,就可以考虑用这个方法。

平方取中法

这个方法计算很简单,假设关键字是1234,那么它的平方就是1522756,再抽取中间的3位就是227,用做散列地址。再比如关键字是4321, 那么它的平方就是18671041,抽取中间的3位就可以是671,也可以是710,用做散列地址。

平方取中法比较适合于不知道关键字的分布,而位数又不是很大的情况。

折叠法

折叠法是将关键字从左到右分割成位数相等的几部分(注意最后一部分位数不够时可以短些),然后将这几部分叠加求和,并按散列表表长,取后几位作为散列地址。

比如:我们的关键字是9876543210,散列表表长为三位,我们将它分为四组987|654|321|0,然后将它们叠加求和987+654+321+0=1962,再求后3位得到散地址为962。

折叠法事先不需要知道关键字的分布,适合关键字位数较多的情况。

除留余数法

取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址。即 H(key) = key MOD p,p<=m。不仅可以对关键字直接取模,也可在折叠、平方取中等运算之后取模。

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

随机数法

选择一随机函数,取关键字的随机值作为散列地址,H(key)=random(key)其中random为随机函数,通常用于关键字长度不等的场合。

三、处理散列冲突的方法

开放定址法

Hi=(H(key) + di) MOD m,i=1,2,…,k(k<=m-1),其中H(key)为散列函数,m为散列表长,di为增量序列

有下列三种取法:

  1. di=1,2,3,…,m-1,称线性探测再散列
  2. di=12,(-1)2,(2)2,(-2)2,±(k)2,(k<=m/2),称二次探测再散列
  3. di=伪随机数序列,称伪随机探测再散列

再散列法

Hi=RHi(key),i=1,2,…,k, RH,H均是不同的散列函数

即在同义词产生地址冲突时计算另一个散列函数地址,直到冲突不再发生,这种方法不易产生“聚集”,但增加了计算时间。

链地址法

将所有关键字为同义词的记录存储在一个单链表中,我们称这种表为同义词子表,在散列表中只存储所有同义词子表的头指针。

链地址法对于可能会造成很多冲突的散列函数来说,提供了绝不会出现找不到地址的保障。当然,这也就带来了查找时需要遍历单链表的性能损耗。

公共溢出区法

这个方法其实就更加好理解,为所有冲突的关键字建立了一个公共的溢出区来存放。

在查找时,对给定值通过散列函数计算出散列地址后,先与基本表的相应位置进行比对,如果相等,则查找成功。如果不相等,则到溢出表去进行顺序查找。如果相对于基本表而言,有冲突的数据很少的情况下,公共溢出区的结构对查找性能来说还是非常高的。

四、散列表查找算法实现

#define success 1
#define unsuccess 0
#define hash_size 12//散列表长度
#define NULLKEY -32768
typedef struct
{
	int* elem;//数据元素存放基址,动态分配数组
	int count;//数据个数
}hash_table;
int m = 0;//散列表表长
bool init_hash_table(hash_table *h)//初始化
{
	m = hash_size;
	h->count = m;
	h->elem = (int *)malloc(m * sizeof(int));
	for (int i = 0; i < m; i++)
		h->elem[i] = NULLKEY;
	return true;
}
int Hash(int key)//散列函数
{
	return key % m;
}
void insert_hash(int key,hash_table * h)
{//插入
	int addr = Hash(key);
	while (!h->elem[addr] == NULLKEY)
		addr = (addr + 1) % m;
		//开放定址法的线性探测(也可以用其他)
	h->elem[addr] = key;
}
int search_hash(hash_table* h, int key,int * addr)
{
	*addr = Hash(key);
	while (h->elem[*addr] != key)
	{
		*addr = (*addr + 1) % m;
		if (h->elem[*addr] == NULLKEY || *addr == Hash(key))
			//循环回到原点或者该位置为无关键字
			return unsuccess;
	}
	return success;
}

五、散列表查找性能分析

散列查找的平均查找长度取决于三个因素:

  • 散列函数是否均匀,散列函数的好坏直接影响着出现冲突的频繁程度

  • 处理冲突的方法,比如线性探测处理冲突可能会产生堆积,显然就没有二次探测法好,而链地址法处理冲突不会产生任何堆积,因而具有更佳的平均查找性能。

  • 散列表的装填因子,所谓的装填因子a=填入表中的记录个数/散列表长度。a标志着散列表的装满的程度。当填入表中的记录越多,x就越大,产生冲突的可能性就越大。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值