Python实现平方探测法散列表
在学习数据结构的时候,因为在网上一时间没有找到相应语言的版本,再加上我囫囵吞枣的看书方式,导致我在写平方探测法时格外困难,分享出来以加深记忆,并且为想要参考的萌新们指条明路。
这篇博客的代码翻译主要来自《数据结构与算法分析c语言描述第二版》,python编写的时候除了python的面向对象的写法和一些魔法方法没有按照原书中来,其主要算法思想来自书中。
开放定址法(Open address hashing)
通过开放定址法解决散列表的哈希冲突,一共分为三种方法,1. 线性探测法 2.平方探测法 3.双散列这三种方法。这篇博客主要来讲第2种方法的具体实现.
在开始之前,我们首先需要引入一个定义来描述散列表表内元素和表大小的比值,装填因子,装填因子主要影响我们在插入时所需要循环的次数,也可以让我们及时对表进行扩充以提高效率。
- 我们将填充因子表示为λ,我们在进行散列查找时,可以对我们进行再散列有一个正确的评估。
- 填充因子λ计算代码(其主要用途为在进行插入时进行判断是否需要进行再散列
def loadFactor(self):
return self.__size / self.__capacity
再散列的实现:
def reHash(self):
OldTable: list = self.__table
newcapacity: int = self.NextPrime(self.__capacity * 2)
self.__capacity = newcapacity
self.__table = []
self.__table = [HashEntry("", "") for _ in range(self.__capacity)]
for i in OldTable:
if i.Status == Status.Exist:
self.insert(i.key, i.value)
平方探测法
顾名思义,在平方探测法的散列表中,单元hi (x)(i [0,n]) 其中hi(x) = (hash(x) + F(i)) mode capacity
- 这里的capacity指的是我们初始化时所分配给列表(数组)的单元个数。
- hash(x) 是我们通过哈希函数算得得哈希值
- F(i)时所选择相应的解决哈希冲突的冲突函数即冲突解决办法 像线性探测的哈希冲突为F(i)=i 而平方探测的冲突函数同理为F(i)= i^2
定理:使用平方探测,且表的大小为素数,那么至少有一半是空的时候,总能够插入一个新的元素
通过定理,我们需要在进行初始化的时候将表的大小尽量定义为素数(质数,顺便帮忘记的人补充一下,素数指的是除了1和它本身,其他自然数无法整除的数)。但如果当我们定义不为质数,那么在进行平方探测时,在还未将整个表填满一半之前,就无法保证在找到一个空单元了。
因此,在我们进行初始化的时候,应该通过程序,即使我们输入的不是素数,也可以计算为一个新的质数并进行初始化。具体代码如下
def IsPrime(self, num: int) -> bool:
'''prime number judgement'''
Max = pow(num, 0.5)
for i in range(2, int(Max) + 1):
if num % i == 0:
return False
return True
def NextPrime(self, num: int):
'''calculate next prime'''
i: int = num
while not self.IsPrime(i):
i += 1
return i
这边我只是用最简单的方法进行判断是否为素数并且重新计算一个新的素数,且算法不为最优。是否使用自行斟酌。
然后,我们将所需要的元素插入相应的单元时,我们需要计算哈希值,相应的哈希算法为
∑
i
=
0
K
e
y
s
i
z
e
−
1
K
e
y
[
K
e
y
S
i
z
e
−
i
−
1
]
∗
3
2
i
\sum_{i=0}^{Keysize-1}Key[KeySize-i-1]*32^{i}
i=0∑Keysize−1Key[KeySize−i−1]∗32i
即,相应的代码为:
'''此处的key值类型根据需要更改'''
def _Hash(self, key: str):
'''calculate hash value'''
HashVal: int = 0
for i in key:
HashVal = (HashVal << 5) + ord(i)
return HashVal
def _hash(self, key: str):
'''mode hash value'''
return self._Hash(key) % self.__capacity
接下来我们最后就是通过冲突函数解决哈希冲突问题,我们说过通过采用平方探测的冲突函数为F(i)=i^2
但在代码中的实现并不理想,所以根据原函数可知F(i)=F(i-1) + i * 2 - 1这个方法也是我们在进行find查找操作时主要的算法。
find方法的pthon实现代码:
def find(self, key: str) -> int:
'''find the key position'''
currentPos = self._hash(key) #哈希值所得结果
collisionNum = 0
while self.__table[currentPos].Status != Status.Empty and self.__table[currentPos].key != key:
collisionNum += 1
currentPos = (currentPos + 2 * collisionNum - 1) % self.__capacity #calculate position F(i) = F(i-1) + 2 * di -1
return currentPos
通过函数直接去看可能有些抽象,但好在这个函数足够简单,我们通过手算或画图也可以轻松算出来。
平方探测的冲突函数运算和插入的大概示意图。
全部代码
from enum import Enum
class Status(Enum):
'''enum element'''
Empty = 0
Exist = 1
Delete = 2
class HashEntry:
def __init__(self, key: str, value: str):
self.key: str = key
self.value: str = value
self.Status: Status = Status.Empty
class HashMap:
def __init__(self, capacity: int):
'''initialize'''
self.__capacity: int = self.NextPrime(capacity)
self.__size: int = 0
self.__table = [HashEntry("", "") for _ in range(self.__capacity)]
def loadFactor(self):
return self.__size / self.__capacity
def IsPrime(self, num: int) -> bool:
'''prime number judgement'''
Max = pow(num, 0.5)
for i in range(2, int(Max) + 1):
if num % i == 0:
return False
return True
def NextPrime(self, num: int):
'''calculate next prime'''
i: int = num
while not self.IsPrime(i):
i += 1
return i
def _Hash(self, key: str):
'''calculate hash value'''
HashVal: int = 0
for i in key:
HashVal = (HashVal << 5) + ord(i)
return HashVal
def _hash(self, key: str):
'''mode hash value'''
return self._Hash(key) % self.__capacity
def reHash(self):
OldTable: list = self.__table
newcapacity: int = self.NextPrime(self.__capacity * 2)
self.__capacity = newcapacity
self.__table = []
self.__table = [HashEntry("", "") for _ in range(self.__capacity)]
for i in OldTable:
if i.Status == Status.Exist:
self.insert(i.key, i.value)
def find(self, key: str) -> int:
'''find the key position'''
currentPos = self._hash(key)
collisionNum = 0
while self.__table[currentPos].Status != Status.Empty and self.__table[currentPos].key != key:
collisionNum += 1
currentPos = (currentPos + 2 * collisionNum - 1) % self.__capacity #calculate position F(i) = F(i-1) + 2 * di -1
return currentPos
def insert(self, key: str, value: str) -> None:
'''insert Key/value'''
Pos = self.find(key)
if self.__table[Pos].Status != Status.Exist:
self.__table[Pos].Status = Status.Exist
self.__table[Pos].key = key
self.__table[Pos].value = value
self.__size += 1
if self.loadFactor() >= 0.5:
self.reHash()
def __str__(self):
l = []
ans = []
m: str = ":"
res: str = ","
for entry in self.__table:
if entry.Status != Status.Empty:
l.append(entry.key)
l.append(entry.value)
ans.append(m.join(l))
l = []
return res.join(ans)
def __len__(self):
return self.__size
if __name__ == '__main__':
'''test'''
hp = HashMap(6)
hp.insert("Koth", "100")
hp.insert("Subv", "352")
hp.insert("Kot", "100")
hp.insert("Ko", "100")
hp.insert("Su", "352")
hp.insert("K", "100")
hp.insert("Subr", "352")
print(hp)
第一次发博客,如果任何错误,欢迎指正