配合前置文章食用更佳,传送门如下:
实现Hash表(一)----手写一个简单hash表
问题描述
目前增删改查我们已经完成了增、改、查,还差"删",实现是否是通过get_valid_index
获取对应的index,之后将对应的数据变为None就行了呢?
问题分析
当然前面我都那么说了吗,答案肯定是no…
为什么呢?考虑如下几个问题:
1.删除了元素之后,假如只是将其置空,get_valid_index是否可以正常工作?
不可以,冲突时会影响后续的搜索,考虑序列A x B none
x代表该元素被删除
当查找B的时候,首先假设映射到A,即A、B对应的hash值相同,向后查找,get_valid_index应该返回B对应的索引
但是这种情况下只会返回x对应的索引,找不到B对应的value
2.除此之外,删除后的位置应该可以进行复用,否则对空间会造成很大浪费
解决办法:
引入一个标记----tombstone----这里我用(None,None)
充当tombstone,用于标记已经删除的位置
但是随之而来的问题:效率下降
多次删除插入之后,你的搜索,可能需要遍历的长度变长
假设平均搜索的长度是1.2,也就是每次调用get_valid_index的时候,平均向后需要继续搜索0.2的距离,多次插入删除之后变为了1.6
,虽然看似变化不大,其实,其多出来的长度,变为了原来的三倍。
解决办法:
周期性删除tombstone,既可以删除tombstone,还可以趁这个机会,将经常搜索的key,变成get_valid_index可以直接获取的index,而不需要向后遍历,效率进一步提高。
python代码实现
-
可以和之前的代码对比,看看那些地方发生了改变
-
注意对none的判断,可以跑出异常,例如
raise ValueError("查找的键值不能为none")
MAX_HASH_TABLE_SIZE = 4096
class DictByHashWithDel:
def __init__(self, max_size=MAX_HASH_TABLE_SIZE):
self.max_size = max_size
self.data_list = [None] * max_size
def get_valid_index(self, key):
"""
:param key:
:return: hash 返回一个hash值
e.g hash('test') ---> 9223025588143776724
hash(1) --->1
"""
index = hash(key) % self.max_size
while True:
if self.data_list[index] is None:
return index
if self.data_list[index] == (None, None):
return index
key_value = self.data_list[index]
if key_value[0] == key:
return index
index += 1
if index == len(self.data_list):
index = 0
def delete(self, key):
"""
:param key:
:return:
删除key所对应的元素
"""
index = self.get_valid_index(key)
self.data_list[index] = (None, None)
def __getitem__(self, key):
"""
:param key: hash表中存储的key
:return: 查找到的值
"""
if key is None:
raise ValueError("查找的键值不能为none")
return
return self.data_list[self.get_valid_index(key)][1]
def __setitem__(self, key, value):
"""
:param key:
:param value:
:return:
实现更新/插入的逻辑
"""
if key is None and value is None:
raise ValueError("无效操作")
return
index = self.get_valid_index(key)
self.data_list[index] = (key, value)
def __iter__(self):
return (x for x in self.data_list if x is not None and x != (None, None))
def __len__(self):
return len([x for x in self])
def __repr__(self):
"""
:return:print的时候会调用这个
"""
from textwrap import indent
pairs = [indent("{} : {}".format(repr(kv[0]), repr(kv[1])), ' ') for kv in self]
return "{\n" + "{}".format(',\n'.join(pairs)) + "\n}"
def __str__(self):
return repr(self)
if __name__ == '__main__':
# Create a hash table
table = DictByHashWithDel()
# Insert some key-value pairs
table['a'] = 1
table['b'] = 34
# Retrieve the inserted values
print(table['a'] == 1 and table['b'] == 34)
# Update a value
table['a'] = 99
# Check the updated value
print(table['a'] == 99)
# Get a list of key-value pairs
# 这里可能会不一样,如果取1024就是对的,默认值会得到[('b', 34), ('a', 99)]
print(list(table) == [('a', 99), ('b', 34)])
table.delete('a')
print(table)
print(len(table))
for i in table:
print(i)
table['a'] = 11
print(table)
print(len(table))
总结
通过对上一篇文章的代码改进,实现了hash表中的删除操作,但是这个操作很繁琐,复杂,还有其他方式可以实现hash表吗?具体内容参见: