Hash表,哈希表是一种容易查找到数据存储项的集合。
哈希表的每个位置通常称为1个槽,可以容纳1个项。例如我们有一个为0的槽,1的槽,2的槽。。。
每个槽初始化为容纳为None
项和槽之间的映射方法称为hash函数,可以理解为数据和索引之间有一个映射关系。
假设使用余数法,数据的余数作为项的索引。
54 % 11 = 10
其中54位数据,10为哈希值,也就是索引
对于数据预处理阶段有许多方法处理
1、分组求和法:
如果我们的数据是电话号码:436-555-4601
取出两位数字作为分组。43,65,55,46,01。将数字相加。为210。
如果说哈希表有11个槽。
哈希值为 210 % 11 =1。
也有求和前每隔一个组做一次反转。
43,56(反转),55,64(反转),01,求和为219
哈希值为219 % 11 = 10
2、平均取中法:
提取一部分数据结果并平方,取中间两位数字。
比如从上述电话取出4和4组成44。
44的平方为1936,取中间两个数字为 93
哈希值为 93 % 11 = 5
字符可以认为是ascii值的序列
def hash(inputString):
#初始化
sum = 0
#遍历字符串
for pos in range(len(inputString)):
sum = sum + ord(inputString)*(pos+1) #根据位置加权求和
return sum
hash函数一定要高效。因为计算hash值是需要耗费时间的。
冲突问题
如果两个数据的哈希值一样,他们会放到同一个槽中,这个时候就发生了碰撞冲突。
一个简单的方法是发生碰撞时会从原始哈希值的位置开始顺序移动找到一个空的槽——线性探测
假如有[77, 44]这组数。
77 % 11 =0
先放入了0号槽
44 % 11 = 0
这个时候本来也要放入0号槽。
但是这个时候0号槽被77占用了。
向下寻找,发现1号槽是空的。44就放入了1号槽。
如果加入一个数31,31 % 11 = 9,放入9号槽。
加入一个数54, 54 % 11 = 10 放入10号槽。
再来一个数 20, 20 % 11 = 9。
9号槽被占用,看10号槽。10号槽又被占用,就从0号槽开始从头搜索,找到2号槽为空。放入20。
画个图可能好理解点。
用这种方式建立的哈希表有一个小问题,就是集中趋势。
如果我们要搜索我们刚刚建立的哈希表中20在哪里(在2号槽)
20 % 11 = 9。我们会从9号槽开始搜索,一直搜索到2号槽。(或者有一个值我们去寻找,可能我们找到一个空槽,说明这个值不存在在哈希表中)
这是因为存在很多相同的哈希值,导致了发生了许多冲突。
有一种方式是加3探头,发生碰撞时候我们直接查看下面第3个槽,直到我们找到一个空值。——扩展线性探测
比如在0号槽发生碰撞,我们直接去看 0+3 = 3号槽。如果为空就放进取。
由于我们用这种加N探头的方法的话(跳过一些槽),我们一定要确保哈希表的大小是素数,确保所有槽都能遍历。
还有一种变种的方法叫二次探测,用等差数列来替代跳过的槽数。比如第一个哈希值是h,查看h+1,查看h+1+3,查看h+1+3+5等等。
链表或者集合结合哈希表的方法,每个槽存放的是一个链表或者集合。发生冲突时候就直接追加到这个槽下的链表或者集合就好了。
下面是一个简单的示例程序,有许多地方可以值得思考和修改的。
class HashTable:
def __init__(self):
self.size = 11 #初始化哈希表的大小
self.slots = [None] * self.size #初始化槽
self.data = [None] * self.size #初始化槽中存放的数据
#定义hash函数
def hashfunction(self,key,size):
return key % size
#定义冲突解决方案
def rehash(self,oldhash,size):
return (oldhash+1)%size #搜索下一个槽,如果超过了size则会从头开始
#存入数据
def put(self,key,data):
hashvalue = self.hashfunction(key,len(self.slots))
#如果找到的这个哈希值的槽为空,则直接将数据存进去
if self.slots[hashvalue] == None:
self.slots[hashvalue] = key
self.slots[hashvalue] = data
# 如果键已经存在则把数据替换
else:
if self.slots[hashvalue] == key:
self.data[hashvalue] = data
else:
nextslot = self.rehash(hashvalue,len(self.slots))
while self.slots[nextslot] != None and self.slots[nextslot] != key:
nextslot = self.rehash(nextslot,len(self.slots)) #当表被写满时候可能会陷入死循环
if self.slots[nextslot] == None:
self.slots[nextslot] = key
self.data[nextslot] = data
else:
self.data[nextslot] = data #替换
#搜索方法
def get(self,key):
startslot = self.hashfunction(key,len(self.slots))
data = None
stop = False
found = False
position = startslot
while self.slots[position] != None and not found and not stop:
if self.slots[position] == key:
found = True
data = self.data[position]
else:
position = self.rehash(position,len(self.slots))
if position == startslot:
stop = True
return data
def __getitem__(self,key):
return self.get(key)
def __setitem__(self,key,data):
self.put(key,data)
H = HashTable()
H[54] = "CAT"
H[26] = "DOG"
print(H.slots)
print(H.data)
print(H[54])