查找算法(II)散列查找 哈希表 Hash

  散列查找 哈希表 Hash

散列查找的基础知识

  散列(Hash)同顺序、链接和索引一样,是存储集合的又一种方法。

  不同的是,散列表(哈希表)在元素的存储位置和它的关键字之间建立了一个对应关系。

  散列存储的基本思想是:以每个元素的关键字K为自变量,通过一个函数(称为哈希函数散列函数)计算出函数值,把这个值(哈希地址散列地址)解释为一块连续存储空间(即数组空间)的单元地址(即下标),将该元素存储到这个单元中。使用的数组空间是线性表进行散列存储的地址空间,所以被称之为散列表哈希表Hash listHash table)。

  哈希函数是一个映像,因此哈希函数的设定很灵活,只要使得任何关键字由此所得的哈希函数值都落在表长允许范围内即可。

  对不同的关键字可能得到同一个哈希地址,这种现象称为冲突collision),具有相同函数值的关键字对该哈希函数来说称作同义词(synonym)。

  冲突很难避免,但发生冲突的可能性却有大有小,这主要与三个因素有关

  1.装填因子(已存入的元素数与散列表空间大小的比值),该值越小,冲突的可能性就越小,但存储空间的利用率就越低,一般这个值控制在0.6~0.9范围内为宜(why?)。

  2.散列函数的选择,若选得好,就可以使散列地址尽可能均匀地分布在散列空间上。

  3.解决冲突的方法,解决冲突的方法也会影响冲突发生的可能性。

  散列存储的两个关键问题就是:1.如何构造哈希函数尽量避免冲突和2.冲突发生后如何解决。

哈希函数的构造

  构造哈希函数的目标是使散列地址能够尽可能均匀地分布在散列空间上,同时使计算尽可能简单。

  通常考虑的因素有:计算哈希函数所需时间;关键字长度;哈希表的大小;关键字的分布情况;记录的查找频率。

  常用的构造哈希函数的方法有:

1.直接定址法

  取关键字或关键字的某个线性函数值为哈希地址。

    H(key)=key或H(key)=a*key+b

  其中a和b为常数,这种哈希函数叫做自身函数。

  这种函数对于不同的关键字不会发生冲突。

2.数字分析法

  取关键字中某些取值较分散的若干数字位组成哈希地址。

  适合于所有关键字已知,且对每一位的取值做出了一定分析。

3.平方取中法

  取关键字平方后的中间几位作为哈希地址。

  因为一个数平方后的中间几位数和数的每一位都相关,使得散列地址具有较好的分散性,具体取的位数由表长决定。

  平方取中法适用于关键字中的每一位取值都不够分散或者较分散的位数小于散列地址所需要的位数的情况。

4.折叠法

  将关键字分割成位数相同的几部分(最后一部分的位数可以不同),然后取这几部分的叠加和(舍去进位)作为哈希地址,这种方法称为折叠法(folding)。

  数位叠加时可以有移位叠加间界叠加(就是把相邻的数镜像叠加)两种方法。

  折叠法适用于关键字的位数较多,而且关键字中每一位上数字分布大致均匀的情况。

5.除留余数法

  取关键字被某个不大于哈希表表长m的数p除后所得的余数为哈希地址。

   H(key)=k%p

  (这里有个疑团啊,严蔚敏的《数据结构》这本书里是如上这么写的,说p必须小于m,但是另一本书:徐孝凯《数据结构实用教程》里说p应该大于m,并且应该取1.1m~1.7m之间的一个素数。我是这么想的,是不是按后一种取法就可以完全避免冲突了,而前一种取法是想着把冲突留在后面再解决。)

  这是一种最常用的构造哈希函数的方法。它不仅可以对关键字直接取模,也可以在折叠、平方取中等运算之后取模。

6.随机数法

  选择一个随机函数,取关键字的随机函数值作为它的哈希地址。

   H(key)=random(key)

  其中random为随机函数。

  通常,当关键字长度不等时采用此法。

处理冲突的方法

  若由某个关键字得到的哈希地址的位置上已经存在别的记录,则发生冲突,需要为该关键字的记录找到另一个“空”的哈希地址。

  在处理冲突的过程中可能得到一个地址序列,若再次得到的哈希地址仍然发生冲突就再求下一个地址,依次类推,直到不发生冲突为止,此时找到的地址为记录在表中的地址。

  通常处理冲突的方法有下列几种:

1.开放定址法

  开放定址法就是从发生冲突的那个单元开始,按照一定的次序,从散列表中查找出一个空闲存储单元的方法。在这种方法中,散列表的空闲单元不仅向散列地址为对应值的同义词元素开放,而且向发生冲突的其他元素(非同义词元素)开放,此方法的名称由此而来。

  从发生冲突的单元起进行查找有多种方法,每一种都对应一定的查找次序或查找路径,产生一个确定的探查序列(即待比较元素的地址序列)。

  Hi=(H(key)+di)%m    i=1,2,...,k k<=m-1

  其中: 为哈希函数;m为哈希表长; 为增量序列,可以有下列取法:

  (1)线性探测再散列(线性探查法)

  di=1,2,3,…,m-1

  线性探测再散列可以保证做到:只要哈希表未填满,总能找到一个不发生冲突的地址。

  但是线性探查容易造成堆积现象,因为探查序列过分集中在发生冲突的单元后面,没有在整个散列空间上分散开。

  (2)二次探测再散列(平方探查法)

   di=12,-12,22,-22,32,…

  (另一种说法是不需要负号的部分)。

  (3)伪随机探测再散列

  di=伪随机数序列

  在处理冲突的过程中有可能会出现两个第一个哈希地址不同的记录争夺同一个后继哈希地址的现象,即在处理同义词的冲突过程中又添加了非同义词的冲突,这种现象称为“二次聚集”,对查找不利。

2.再哈希法

   Hi=RHi(key)

  RHi是各自不同的i个哈希函数,即在同义词产生地址冲突时计算另一个哈希函数地址,直到冲突不再发生。这种方法不易产生聚集,但增加了计算的时间。

3.链地址法

  将所有关键字为同义词的记录存储在同一线性链表中。

  假设某哈希函数产生的哈希地址在区间[0,m-1]上,则设立一个指针型向量:

  Chain ChainHash[m]

  其每个分量的初始状态都是空指针。凡哈希地址为i的记录都插入到头指针为ChainHash[i]的链表中。在链表中的插入位置可以在表头或表尾;也可以在中间,以保持同一线性链表中按关键字有序。

4.建立一个公共溢出区

  假设哈希函数的值域为[0,m-1],则设向量HashTable[0…m-1]为基本表,每个分量存放一个记录,另设立向量OverTable[0…v]为溢出表。所有关键字和基本表中关键字为同义词的记录,不管它们由哈希函数得到的哈希地址是什么,一旦发生冲突,都填入溢出表。

 

哈希表的查找

  在哈希表上进行查找的过程和哈希造表的过程基本一致。

  给定K值,根据造表时设定的哈希函数求得哈希地址,若表中此位置上没有记录,则查找不成功;

  若有记录,则比较关键字,若和给定值相同,则查找成功;若关键字不同,则根据造表时设定的处理冲突的方法找“下一地址”,直至哈希表中某个位置为“空”或者表中所填记录的关键字等于给定值时为止。

  值得提醒的是,若要在非链地址处理冲突的哈希表中删除一个记录,则需在该记录的位置上填入一个特殊的符号,以免找不到在它之后填入的“同义词”的记录。

posted @ 2012-11-13 21:15  圣骑士wind  阅读(2466)  评论(0编辑  收藏  举报