散列表简述
这是一种查找效率为O(1)的结构,又称之为散列hashing;
存放数据的这种结构,有槽号;存放数据时建立槽号和数据的映射关系,然后查找时,用同样的哈希函数去计算出槽号,看槽号里有没有就可以了。如,求余就是一个好的散列函数。
关键问题一:散列函数中碰到冲突怎么办?
所以我们希望有一种完美的哈希函数,能够没有冲突,显然在有限的输出里是不现实的–所以我们只能尽量创造出比较好的函数来让冲突尽可能的小。
常见有MD5,SHA0 , SHA1 , SHA256 , SHA224 , SHA384 , SHA512 .其中MD5是128位,16个字节。SHA0/SHA1是输出的160位(20个字节)。
Map数据结构实现
Map实现的是用包含着一种 键-值 匹配的结构 对象,就像是字典一样;
其中核心的功能是实现通过快速找到某个键,从而得到它的值;所以为了实现0(1)的算法,我们可以采用散列表;
下面是一种最简单的实现散列表的方式:
class HashTable:
def __init__(self):
self.size=11
self.slots=[None]*self.size
self.data=[None]*self.size
def hashfunction(self,key):#根据key值选一个位置
pass
def rehash(self,hashvalue):#如果第一次没找到合适的,用rehash函数再找下一个可用的位置
pass
def put(self,key,value):
hashvalue=self.hashfunction(key)
if self.slots[hashvalue]==None or self.slots[hashvalue]==key:
self.slots[hashvalue]=key
self.data[hashvalue]=value
else:
nextslot=self.rehash(hashvlaue)
while self.slots[nextslot]!=None and self.slots[nextslot]!=key:
nextslot=self.rehash(nextslot)
self.slots[nextslot]=key
self.data[nextslot]=value
def get(self,key):
pass
审视整个实现代码,散列表的核心代码有两个–put函数和get函数。put函数具有key和value参数,用来将一对 键-值放进结构里,get函数就是通过key去选择相应的value,如果没有的话返回false,或者爆出异常。
实现思路:
通过两个同样大小的列表来实现槽和值,用下标数来做对应关系:
散列表的存放原则使用hashfunction和rehash函数,来通过key算出下标;
处理冲突的方法:如果碰到冲突了,就后延,后延几个,怎么算。由rehash决定。
比较关键的get函数实现
def get(self,key):
hashvalue=self.hashfunction(key)
if self.slots[hashvalue]==key:
return self.data[hashvalue]
else:
times=1
nextslot=self.rehash(hashvalue)
while self.slots[nextslot]!=None and self.slots[nextslot]!=key and times<12:
nextslot=self.rehash(nextslot)
times+=1
if self.slots[nextslot]==None or times>12:
raise NotExist
else:
return self.data[nextslot]
get函数的实现思路和put函数差不多,思路是反过来的:先同样用hashfunction去计算,如果不是对应的key值,则再rehash一下。
注:在get函数中,考虑了结构全部满了都没有key值的情况,用times来判断,其实在put函数里也存在这种情况,只是没写。 但其实可以在更加源头处解决put函数的情况,就是put时判断下结构体存储数值是否已经满了。这里的12是self.size
简单的哈希函数和再哈希的实现
def hashfunction(self,key):#根据key值选一个位置
return key%self.size
def rehash(self,hashvalue):#如果第一次没找到合适的,用rehash函数再找下一个可用的位置
return (hashvalue+1)%self.size
这里用最常用也是最简单的一种哈希函数–取余;rehash的方法就是直接再+1继续取余就行。
其他细节
1、不简化的put
这里的put函数其实经过了我的简化,就是碰到None或者碰到key值,都使用同样的操作,这是为了简化代码方便理解。在碰到key时,直接更新value值就可以,这里客气区分开来:
def put(self,key,value):
hashvalue=self.hashfunction(key)
if self.slots[hashvalue]==None:
self.slots[hashvalue]=key
self.data[hashvalue]=value
else:
if self.slots[hashvalue]==key:
self.data[hashvalue]=value
else:
nextslot=self.rehash(hashvalue)
while self.slots[nextslot]!=None and self.slots[nextslot]!=key:
nextslot = self.rehash(nextslot)
if self.slots[nextslot]==None:
self.slots[nextslot]=key
self.data[nextslot]=value
else:
self.data[nextslot]=value
如上就是不简化的完整代码。
2、用[]直接表示结构体的存取
对于我们新建的这个哈希表:
hslist=HashTable(10)
hslist.put(3,'wa')
print(hslist.slots)
print(hslist.data)
print(hslist.get(3))
一般使用这样的方法去调用,但是略显麻烦,定义一个键值的数据结构,我们往往希望能够更加直观的去表示,比如如下这样:
hs=HashTable(11)
hs[103]='wa'
print(hs[103])
如果想要这样是,只要在类里再加上相应的特殊函数就可以了。
def __getitem__(self, item):
return self.get(item)
def __setitem__(self, key, value):
return self.put(key,value)
这里的魔术方法就可以让结构体直接使用[]去进行调用,非常的方便。