算法导论例程——哈希表

哈希表(hash table)是普通数组概念的推广,对于一个比较小的规模的数据,我们对其存储采用的是将其存在一个等规模的数组中,并且直接根据数组下标对其进行寻址,这被称为直接寻址法。

而当数据规模增大到一定程度时,我们采用直接寻址法进行遍历时会打打加重程序的时间复杂度,而一种理想的解决办法就是根据数据的某一关键字(这里假设数据元素含有其他卫星数据,这在实际问题中是很常见的)动态的分配存储空间,将数据所需的存储空间大小降低下来,这里我们需要的算法成为哈希算法h(key),一般认为他的结果为自然数,映射为数组的下标,也即哈希表槽的位置。

那么接下来的问题就是——如何选择一个合理的哈希算法,是数据尽量均衡合理地分布,不发生冲突(collision)。

这里介绍三种哈希算法。

除法哈希法

h(k) = k mod 701,指的是关键字对槽数m取模,得到一个较为均衡的排布在槽数内的哈希值,这里槽数的选择不建议选择2的整数次幂,会造成数据一定程度上的分布不均。

乘法哈希法

h(k) = m * (k * A mod !),指的是关键字乘以一个常数A(0 < A < 1),取小数部分乘以槽数。

全域法

全域法会综合以上各种函数的特点,就像快速排序算法采用随机数确定一个分界值,保证最坏情况不会总是发生,这里全域法在接收到一个关键字时,会随机的从设计好的哈希算法中挑选一个出来,使遇到两个不同的关键字k,l时,h(k) == h(l)的概率不超过1/m(从m个槽中随机选一个的概率)。

然而无论是哪种算法都无可避免的会遇到冲突问题,接下来两种解决冲突问题的方法。

链接法

很简单,就是将被哈希到一个槽中的数据都存储在一个链表中,双向链表提高效率,将链表的头指针放在槽中。

开放寻址法

首先引进装载因子的概念,a = n /m,即每个槽中平均含有的数据数。

这种方法指的是每一个数据都放在哈希表的表项中,即每一个槽中要么有一个元素,要么为NIL,当查找某个元素时,要系统的检查所有表项,直到找到需要的元素,或最终查明该元素不再表中。

int hash_insert(int T[], int key)
{
	int i = 0;
	do
	{
		int j = hash(key, i);
		if (T[j] == NIL)
			T[j] = key;
		return j;
		else
			i++;
	} while (i <= m);
	return -1;                     //overflow
}

这里哈希算法接受参数i,使之成为一个二元映射,通过i的调节使其遍历整个哈希表来寻找空槽。类比可以写出删除数据的函数。


完全哈希法

在链接法中将哈希到同一个槽中的数据存储在链表中,而完全哈希顾名思义则是将他们再存放在一个哈希表(第二级哈希)中,这样借助哈希表的探查算法,也许会获得比搜索链表更高的效率。


最后当然是对哈希表的寻址,也叫探查(probe)方法了。

线性探查

给定一个简单的哈希算法h'(k),该探查采用的函数为h(k,i) = (h'(k) + i) mod m添加了随机性,但是随着被占用槽数的增加会出现一次群集现象。

二次探查

h(k,i) = (h'(k) + c1 * i + c2 * i ^ 2),这里引进了i的二次项,使后续的探查位置多一个偏移量,降低了一次群集,但是可能会导致另一种轻度的群集——二次群集,且受限于哈希表的大小,c1 c2的大小受到限制。

双重哈希

双重哈希是用于开放寻址法最好的探查方法之一,它需要两个基础的哈希函数h1,h2,h(k,i) = (h1(k) + i * h2(k)) mod m,这里偏移量不会像之前那两种方法显得“有迹可循”,它产生的排列具有随机选择排列的许多特性。


你可能感兴趣的:(算法导论)