所谓的开放定址法就是一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入。
fi ( key ) = ( f ( key ) + di ) MOD m (di=1,2,3,4,…,m-1)
会出现不是同义词却需要争夺一个地址的情况,我们称这种情况为堆积。
关键字集合『12,67,56,16,25,37,22,29,15,47,48,34』
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
关键字 | 12 | 25 | 37 | 15 | 16 | 29 | 48 | 67 | 56 | 22 | 47 |
当最后一个key=34,f(key)=10,与22的位置冲突。可是22后面没有空位了,虽然可以不断求余数后得到结果,但是效率太低了。
使用12,-12,22,-22,…,q2,-q2,q<=m/2就可以双向寻找可能的空位子,增加平方运算时为了不让关键字都聚集在某一块区域。称之为二次探测法。
fi ( key ) = ( f ( key ) + di ) MOD m (di=12,-12,22,-22,…,q2,-q2,q<=m/2)
在冲突时,对于位移量di采用随机函数计算得到,我们称之为随机函数。
这里的随机其实是伪随机数。伪随机是说如果我们设置的随机种子相同,则不断调用随机函数可以生成一个不会重复的数列。我们在查找时候,用相同的随机种子,它每次得到的数列都是相同的,相同的di当然可以得到相同的散列地址。
fi ( key ) = ( f ( key ) + di ) MOD m (di是一个随机数列)
对于我们的散列函数来说,我们可以事先准备多个散列函数。
fi ( key ) = RHi ( key ) (i=1,2,…,k)
RHi 就是不同的散列函数,可以将前面说散列表查找及其函数的除留余数、折叠、平方取中全部用上。每当出现散列地址冲突,就换一个散列函数计算。这种方法能够使得关键字不产生聚集,但是相应地会增加计算的时间。
思路换一下,为什么有冲突就要换地方呢?我们直接在原地不行吗?于是有了链地址法。
将所有关键字为同义词的记录存储在一个单链表中,我们称之为同义词子表,在散列表中只存储所有同义词子表的头指针。
对于关键字集合{12,67,56,16,25,37,22,9,15,47,48,34},我们用前面的12作为除数,进行除留余数法,可以得下如下结构:
链地址法对于可能造成很多冲突的散列函数来说,提供了绝不会出现找不到地址的保证。当然,这也就带来了查找时需要遍历单链表的性能损耗。
这个方法更好理解,当发生冲突的时候,凡是冲突的跟我走,给这些冲突找个地儿呆着。我们为所有冲突的关键字建立一个公共的溢出区来存放。
就前面的例子而言,我们共有三个关键字{37,48,34}与之前的关键字有冲突,那么将它们存储在溢出表中。
在查找的时候,对于给定值通过散列函数计算出散列地址之后,先与基本表对应位置进行比对,如果相等,则查找成功;如果不相等,则到溢出表进行顺序查找。如果相对于基本表而言,有冲突的数据很少的情况下,公共溢出区的结构对查找性能来说还是非常高的。