开放定址散列法和再散列


目录

  1. 开放定址法
  2. 再散列
  3. 代码实现

1 开放定址散列法

前面利用分离链接法解决了散列表插入冲突的问题,而除了分离链接法外,还可以使用开放定址法来解决散列表的冲突问题。

开放定址法在遇见冲突情形时,将会尝试选择另外的单元,直到找到空的单元为止,一般来说,单元h0(X), h1(X), h2(x)为相继尝试的单元,则hi(X)=(Hash(X)+F(i)) mod TableSize,其中F(i)即为冲突解决的探测方法,

开放定址法中的探测方法的三种基本方式为,

  1. 线性探测法:探测步进为线性增长,最基本的方式为F(i)=i
  2. 平方探测法:探测步进为平方增长,最基本的方式为F(i)= i2
  3. 双散列:探测方法的步进由一个新的散列函数决定,最基本的方式为F(i)= i*Hash2(i),通常选择Hash2(i)=R-(X mod R),其中R为小于TableSize的素数。

2 再散列

对于使用平方探测的开放定址散列法,当元素填得太满的时候,操作运行的时间将消耗过长,而且插入操作有可能失败,此时则可以进行一次再散列来解决这一问题。

再散列会创建一个新的散列表,新的散列表大小为大于原散列表大小2倍的第一个素数,随后将原散列表的值重新散列至新的散列表中。

这一操作的开销十分大,但并不经常发生,且在发生前必然已经进行了多次插入,因此这一操作的实际情况并没有那么糟糕。

散列的时机通常有几种,

  1. 装填因子达到一半的时候进行再散列
  2. 插入失败时进行再散列
  3. 达到某一装填因子时进行散列

3 代码实现

完整代码

 from functools import partial as pro
from math import ceil, sqrt
from hash_table import HashTable, kmt_hashing class RehashError(Exception):
pass class OpenAddressingHashing(HashTable):
def __init__(self, size, hs, pb, fn=None, rf=0.5):
self._array = [None for i in range(size)]
self._get_hashing = hs
self._hashing = hs(size) if not fn else fn
self._probing = pb
self._rehashing_factor = rf def _sniffing(self, item, num, hash_code=None):
# Avoid redundant hashing calculation, if hashing calculation is heavy, this would count much.
if not hash_code:
hash_code = self._hashing(item)
return (hash_code + self._probing(num, item, self.size)) % self.size def _get_rehashing_size(self):
size = self.size * 2 + 1
while not is_prime(size):
size += 1
return size def rehashing(self, size=None, fn=None):
if not size:
size = self._get_rehashing_size()
if size <= (self.size * self.load_factor):
raise RehashError('Rehash size is too small!')
array = self._array
self._array = [None for i in range(size)]
self._hashing = self._get_hashing(size) if not fn else fn
self.insert(filter(lambda x: x is not None, array)) def find(self, item):
hash_code = ori_hash_code = self._hashing(item)
collision_count = 1
value = self._array[hash_code] # Build up partial function to shorten time consuming when heavy sniffing encountered.
collision_handler = pro(self._sniffing, hash_code=ori_hash_code) while value is not None and value != item:
hash_code = collision_handler(item, collision_count)
value = self._array[hash_code]
collision_count += 1
return value, hash_code def _insert(self, item):
if item is None:
return
value, hash_code = self.find(item)
if value is None:
self._array[hash_code] = item
if self.load_factor > self._rehashing_factor:
self.rehashing() def is_prime(num): # O(sqrt(n)) algorithm
if num < 2:
raise Exception('Invalid number.')
if num == 2:
return True
for i in range(2, ceil(sqrt(num))+1):
if num % i == 0:
return False
return True def linear_probing(x, *args):
return x def square_probing(x, *args):
return x**2 def double_hashing(x, item, size, *args):
r = size - 1
while not is_prime(r):
r -= 1
return x * (r - (item % r)) def test(h):
print('\nShow hash table:')
h.show() print('\nInsert values:')
h.insert(range(9))
h.show() print('\nInsert value (existed):')
h.insert(1)
h.show() print('\nInsert value (collided):')
h.insert(24, 47)
h.show() print('\nFind value:')
print(h.find(7))
print('\nFind value (not existed):')
print(h.find(77)) print('\nLoad factor is:', h.load_factor) if __name__ == '__main__':
test(OpenAddressingHashing(11, kmt_hashing, linear_probing))
print(30*'-')
test(OpenAddressingHashing(11, kmt_hashing, square_probing))
print(30*'-')
test(OpenAddressingHashing(11, kmt_hashing, double_hashing))

分段解释

首先导入几个需要的模块,以及散列表类和散列函数(具体实现参考文末相关阅读),并定义一个再散列异常

 from functools import partial as pro
from math import ceil, sqrt
from hash_table import HashTable, kmt_hashing class RehashError(Exception):
pass

定义一个开放定址散列表类,接收参数包括,散列表初始大小size,散列函数的生成函数hs,探测函数pb,指定散列函数fn,再散列因子rf。当指定了散列函数时,使用指定的散列函数,否则使用传入的生成函数,根据散列表大小获得一个默认的散列函数。

 class OpenAddressingHashing(HashTable):
def __init__(self, size, hs, pb, fn=None, rf=0.5):
self._array = [None for i in range(size)]
self._get_hashing = hs
self._hashing = hs(size) if not fn else fn
self._probing = pb
self._rehashing_factor = rf

定义_sniffing方法,嗅探方法用于计算下一个嗅探位置。

Note: 此处为了避免冗余计算,开放一个参数供散列值传入,当进行同一个插入的不同嗅探时,其原始散列值是不变的,因此这里可以配合后面的偏函数,在多次嗅探中固定这一参数,从而避免多次散列函数的计算。这对于复杂的散列函数来说可以减少嗅探计算时间。

     def _sniffing(self, item, num, hash_code=None):
# Avoid redundant hashing calculation, if hashing calculation is heavy, this would count much.
if not hash_code:
hash_code = self._hashing(item)
return (hash_code + self._probing(num, item, self.size)) % self.size

定义_get_rehashing_size方法,用于计算需要再散列时新散列表的大小,通常为大于当前表大小2倍的第一个素数。

     def _get_rehashing_size(self):
size = self.size * 2 + 1
while not is_prime(size):
size += 1
return size

定义rehashing方法,用于进行再散列操作,

  1. 若没有指定再散列大小,则使用默认方式计算,
  2. 当传入的再散列大小小于已有元素数量时,引发再散列异常,
  3. 保存原始散列表信息,并新建一个散列表,更新散列函数,
  4. 利用新的散列函数,遍历原始散列表并插入新的散列表中。
     def rehashing(self, size=None, fn=None):
if not size:
size = self._get_rehashing_size()
if size <= (self.size * self.load_factor):
raise RehashError('Rehash size is too small!')
array = self._array
self._array = [None for i in range(size)]
self._hashing = self._get_hashing(size) if not fn else fn
self.insert(filter(lambda x: x is not None, array))

定义find方法,用于查找散列表内的指定元素,

Note: 这里使用偏函数处理嗅探函数,减少散列计算,利用嗅探函数循环嗅探新的位置,直到找到目标元素或None,此时返回元素值或None和对应的散列值。

     def find(self, item):
hash_code = ori_hash_code = self._hashing(item)
collision_count = 1
value = self._array[hash_code] # Build up partial function to shorten time consuming when heavy sniffing encountered.
collision_handler = pro(self._sniffing, hash_code=ori_hash_code) while value is not None and value != item:
hash_code = collision_handler(item, collision_count)
value = self._array[hash_code]
collision_count += 1
return value, hash_code

定义_insert方法,唯一的区别在于,当装载因子大于再散列因子时,需要进行一次再散列操作。

     def _insert(self, item):
if item is None:
return
value, hash_code = self.find(item)
if value is None:
self._array[hash_code] = item
if self.load_factor > self._rehashing_factor:
self.rehashing()

定义一个素数判断函数,用于计算一个值是否为素数,时间复杂度为O(sqrt(n))。

 def is_prime(num):  # O(sqrt(n)) algorithm
if num < 2:
raise Exception('Invalid number.')
if num == 2:
return True
for i in range(2, ceil(sqrt(num))+1):
if num % i == 0:
return False
return True

接着定义三个探测函数,分别为线性探测、平方探测和双散列。

 def linear_probing(x, *args):
return x def square_probing(x, *args):
return x**2 def double_hashing(x, item, size, *args):
r = size - 1
while not is_prime(r):
r -= 1
return x * (r - (item % r))

最后定义一个测试函数,并对三种探测函数分别进行测试。

 def test(h):
print('\nShow hash table:')
h.show() print('\nInsert values:')
h.insert(range(9))
h.show() print('\nInsert value (existed):')
h.insert(1)
h.show() print('\nInsert value (collided):')
h.insert(24, 47)
h.show() print('\nFind value:')
print(h.find(7))
print('\nFind value (not existed):')
print(h.find(77)) print('\nLoad factor is:', h.load_factor) if __name__ == '__main__':
test(OpenAddressingHashing(11, kmt_hashing, linear_probing))
print(30*'-')
test(OpenAddressingHashing(11, kmt_hashing, square_probing))
print(30*'-')
test(OpenAddressingHashing(11, kmt_hashing, double_hashing))

三种探测函数测试项及对应结果如下,

初始建立散列表

     print('\nShow hash table:')
h.show()

三者结果均相同,

Show hash table:
[0] None
[1] None
[2] None
[3] None
[4] None
[5] None
[6] None
[7] None
[8] None
[9] None
[10] None

接着尝试插入超过装填因子一半的元素数量,此时散列表会自动进行再散列

Insert values:
[0] 0
[1] 1
[2] 2
[3] 3
[4] 4
[5] 5
[6] 6
[7] 7
[8] 8
[9] None
[10] None
[11] None
[12] None
[13] None
[14] None
[15] None
[16] None
[17] None
[18] None
[19] None
[20] None
[21] None
[22] None

接着插入已存在的元素

     print('\nInsert value (existed):')
h.insert(1)
h.show()

结果不变,

再插入两个会造成冲突的元素,三者结果分别如下,可以看到,不同的探测函数将元素插入到了散列表的不同位置。

Linear_probing | Square_probing | Double_hashing
[0] 0 | [0] 0 | [0] 0
[1] 1 | [1] 1 | [1] 1
[2] 2 | [2] 2 | [2] 2
[3] 3 | [3] 3 | [3] 3
[4] 4 | [4] 4 | [4] 4
[5] 5 | [5] 5 | [5] 5
[6] 6 | [6] 6 | [6] 6
[7] 7 | [7] 7 | [7] 7
[8] 8 | [8] 8 | [8] 8
[9] 24 | [9] None | [9] None
[10] 47 | [10] 24 | [10] None
[11] None | [11] None | [11] 47
[12] None | [12] None | [12] None
[13] None | [13] None | [13] None
[14] None | [14] None | [14] None
[15] None | [15] None | [15] 24
[16] None | [16] None | [16] None
[17] None | [17] 47 | [17] None
[18] None | [18] None | [18] None
[19] None | [19] None | [19] None
[20] None | [20] None | [20] None
[21] None | [21] None | [21] None
[22] None | [22] None | [22] None

最后,测试查找函数以及获取装填因子,由于探测函数不同,因此查找不存在结果时,最后处在的位置也不同。

--------------------------------------
Linear_probing
--------------------------------------
Find value:
(7, 7) Find value (not existed):
(None, 11) Load factor is: 0.4782608695652174 --------------------------------------
Square_probing
--------------------------------------
Find value:
(7, 7) Find value (not existed):
(None, 9) Load factor is: 0.4782608695652174 --------------------------------------
Double_hashing
--------------------------------------
Find value:
(7, 7) Find value (not existed):
(None, 21) Load factor is: 0.4782608695652174

相关阅读


1. 散列表

2. 分离链接法

Python与数据结构[4] -> 散列表[2] -> 开放定址法与再散列的 Python 实现的更多相关文章

  1. C# Dictionary源码剖析---哈希处理冲突的方法有:开放定址法、再哈希法、链地址法、建立一个公共溢出区等

    C# Dictionary源码剖析 参考:https://blog.csdn.net/exiaojiu/article/details/51252515 http://www.cnblogs.com/ ...

  2. Python与数据结构[4] -> 散列表[1] -> 分离链接法的 Python 实现

    分离链接法 / Separate Chain Hashing 前面完成了一个基本散列表的实现,但是还存在一个问题,当散列表插入元素冲突时,散列表将返回异常,这一问题的解决方式之一为使用链表进行元素的存 ...

  3. java 解决Hash(散列)冲突的四种方法--开放定址法(线性探测,二次探测,伪随机探测)、链地址法、再哈希、建立公共溢出区

    java 解决Hash(散列)冲突的四种方法--开放定址法(线性探测,二次探测,伪随机探测).链地址法.再哈希.建立公共溢出区 标签: hashmaphashmap冲突解决冲突的方法冲突 2016-0 ...

  4. 开放定址法——线性探测(Linear Probing)

    之前我们所采用的那种方法,也被称之为封闭定址法.每个桶单元里存的都是那些与这个桶地址比如K相冲突的词条.也就是说每个词条应该属于哪个桶所对应的列表,都是在事先已经注定的.经过一个确定的哈希函数,这些绿 ...

  5. 开放定址法——平方探测(Quadratic Probing)

    为了消除一次聚集,我们使用一种新的方法:平方探测法.顾名思义就是冲突函数F(i)是二次函数的探测方法.通常会选择f(i)=i2.和上次一样,把{89,18,49,58,69}插入到一个散列表中,这次用 ...

  6. Python与数据结构[0] -> 链表/LinkedList[0] -> 单链表与带表头单链表的 Python 实现

    单链表 / Linked List 目录 单链表 带表头单链表 链表是一种基本的线性数据结构,在C语言中,这种数据结构通过指针实现,由于存储空间不要求连续性,因此插入和删除操作将变得十分快速.下面将利 ...

  7. Python与数据结构[4] -> 散列表[0] -> 散列表与散列函数的 Python 实现

    散列表 / Hash Table 散列表与散列函数 散列表是一种将关键字映射到特定数组位置的一种数据结构,而将关键字映射到0至TableSize-1过程的函数,即为散列函数. Hash Table: ...

  8. Python数据结构——散列表

    散列表的实现常常叫做散列(hashing).散列仅支持INSERT,SEARCH和DELETE操作,都是在常数平均时间执行的.需要元素间任何排序信息的操作将不会得到有效的支持. 散列表是普通数组概念的 ...

  9. 【Python算法】哈希存储、哈希表、散列表原理

    哈希表的定义: 哈希存储的基本思想是以关键字Key为自变量,通过一定的函数关系(散列函数或哈希函数),计算出对应的函数值(哈希地址),以这个值作为数据元素的地址,并将数据元素存入到相应地址的存储单元中 ...

随机推荐

  1. eclipse启运时显示:Workspace in use or cannot be created, choose a different one

    The time when I runned Eclipse in my computer, it has this information displayed: WorkSpace *** in u ...

  2. 2017 Multi-University Training Contest - Team 3 Kanade's trio(字典树+组合数学)

    题解: 官方题解太简略了orz 具体实现的方式其实有很多 问题就在于确定A[j]以后,如何找符合条件的A[i] 这里其实就是要提前预处理好 我是倒序插入点的,所以要沿着A[k]爬树,找符合的A[i] ...

  3. Citrix Netscaler负载均衡算法

    Citrix Netscaler负载均衡算法 http://blog.51cto.com/caojin/1926308 众所周知,作为新一代应用交付产品的Citrix Netscaler具有业内领先的 ...

  4. [洛谷P2032]扫描

    题目大意:有一串数,有一个长度为k的木板,求木板每次移动后覆盖的最大值 题解:单调队列 C++ Code: #include<cstdio> using namespace std; co ...

  5. Visual Studio调试之断点技巧篇补遗

    原文链接地址:http://blog.csdn.net/Donjuan/article/details/4649372 讲完Visual Studio调试之断点技巧篇以后,翻翻以前看得一些资料和自己写 ...

  6. MySQL使用笔记(六)条件数据记录查询

    By francis_hao    Dec 17,2016 条件数据记录查询 mysql> select field1,field2-- from table_name where 条件; 其中 ...

  7. C语言指针大杂烩

    By francis_hao Oct 31,2016 指针数组和数组指针 指针数组本身是个数组,数组的内容是指针.形如char *pa[].由于[]优先级高于*,pa先于[]结合表示pa是一个数组,p ...

  8. Codeforces 931.D Peculiar apple-tree

    D. Peculiar apple-tree time limit per test 1 second memory limit per test 256 megabytes input standa ...

  9. Input操作文件

    在HTML表单中,可以上传文件的唯一控件就是<input type="file">. 注意:当一个表单包含<input type="file" ...

  10. linux查看操作系统是多少位

    有三种方法: 1.echo $HOSTTYPE 2.getconf LONG_BIT,此处不应该是getconf WORD_BIT命令,在64位系统中显示的是32 3.uname -a 出现" ...