散列表(Hash Table)

散列表(hash table): 也称为哈希表. 根据wikipedia的定义:是根据关键字(Key value)而直接访问在内存存储位置的数据结构。也就是说,它通过把键值通过一个函数的计算,映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数称做散列函数(hash function),存放记录的数组称做散列表(hash table)

为了方便理解概念.举个与散列表原理相似的实际生活中应用例子--电话簿.一般的电话簿是按照电话用户的姓的首字母a-z顺序进行排列的,首字母相同的按照姓的第二个字母进行排列,以此类推(可能还有其他的规则,比如地点,时间,或者几种规则的混合).当我们要查找某人的电话号码时,我们会根据用户的姓推导出对应的首字母,然后再按照首字母去对应的子目录下查找和该用户姓名对应的电话号码.在这个过程中,"用户的姓名"对应于散列表中的关键字(key),"推导的规则"好比散列表中的哈希函数(hash function).号码簿即散列表. 散列表是根据将关键字代入哈希函数中求得的值进行存储,而非根据关键字. 那就有会存在这样的一种情况,如果将不同的关键字代入哈希函数求得的值相同,那散列表如何处理呢? 这种不同关键字而哈希函数值相同的情况称作碰撞(collision).

 

对于散列表的设计,建立以及建立后数据的查找,插入和删除等操作的性能,问题的重心主要在两个方面:哈希函数和碰撞。 

哈希函数:

对于一个好的哈希函数来讲,每个关键字都等可能地分配到m(假设散列表提供了m个槽位(slot))的槽位中,并且与其他关键字已经分配到哪个槽位无关。确切来讲要求该函数是符合独立同分布的特点。但是在实际中,我们很难找出关键字的分布规律. 一种好的方法是以独立于数据中可能存在的任何模式的方法导出散列值(算法导论 第二版). 对于这句话我的理解是: 在实际产生的数据中,数据的内容,格式等很大程度上可能存在着联系和聚集的情况, 即分布不均匀, 而散列表顾名思义,需要"散列",如果采用数据原有的模式如内容,格式等与"散列"的精神冲突的可能性很大.

下面介绍几种哈希函数的设计方案:

将关键字解释为自然数: 我们都知道计算机的灵魂只有"0"和"1", 与它们比较接近的是数字,或者更小范围的整数,自然数. 如果能够将信息或数据转化为自然数,计算机处理数字要方便的多. 同时对于哈希函数来讲,处理数字更便利.基于这两点,散列表要求的关键字,我们可以将原始关键字进行转化自然数形式.

1.除法散列法

散列函数: h(k)= k mod m

m: 散列表槽数  k:关键字   h(k): 数组地址索引

函数关键: m 的选择,一般选择与2的整数次幂不太接近的质数

2. 乘法散列表

散列函数: 

两个步骤:

1. 用关键字k乘上常数A(0<k<1), 取出kA的小数部分。

2. 用m乘以这个小数部分,再对乘积的结果取底。

优点: 对于m的取值没有太多的要求,一般选择以2为底的某个次幂。 A的取值为0.618 033 比较理想。

 

无论怎么设计哈希函数,对于"碰撞"情况的发生都是不能绝对避免的,所以接下来的问题是怎么解决"碰撞"的情况. 现在普遍的方法有链接法,和开放寻址法.下面阐述的炒作都忽略卫星数据,只考虑关键字。

链接法:

顾名思义,是通过链表将碰撞的数据进行相互链接到同一槽上. 对于插入,删除,查找操作,在完成查找对应槽的操作后(O(1)),接下来的既是对链表进行操作。假设有n个关键字,m 条槽

成功查找:O(1+n/m), 失败查找:O(1+n/m)

插入有不同的规则, 一般是插入到链表头部(即与槽相连接部分).

开放寻址法:

将所有的元素都存入到散列表中,不通过链表来解决碰撞问题,如果产生碰撞,通过改变参数来使用哈希函数中重新寻找空槽. 由于要求所有元素都存入散列表中,这就需要n<=m,及关键字总数小于槽数。

优点: 不需要使用指针,节省了空间,潜在的效果是减少碰撞,提高了查找速度。

插入: 对于每一个关键字k, 都可以按照如下的序列<h(k,0),h(k,1)...h(k,n-1)>,依次进行,直到查找到空槽插入关键字为止.

查找: 和插入算法是一样的,只是查找过程中如果碰到一个空槽就停止,返回信息

删除: 由于查找的规则,所以删除一项时,应该将该空槽进行标记,如Deleted

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2014-02-24 10:08  游-游  阅读(317)  评论(0编辑  收藏  举报