前情提要:
hashmap:python语言中的dict底层是基于hashmap结构实现的,dict的使用就不说了。
关键一点是,hashmap可以在一堆数据中,很快的根据key,找到value,这个关键点主要是由hash函数实现的。
详细原理请看《大话数据结构》一书的8.9章节,我觉得讲得很好。。
class MyHash(object):
def __init__(self, length=10):
self.length = length
self.items = [[] for i in range(self.length)]
def hash(self, key):
"""计算该key在items哪个list中,针对不同类型的key需重新实现"""
return key % self.length
def equals(self, key1, key2):
"""比较两个key是否相等,针对不同类型的key需重新实现"""
return key1 == key2
def insert(self, key, value):
index = self.hash(key)
if self.items[index]:
for item in self.items[index]:
if self.equals(key, item[0]):
# 添加时若有已存在的key,则先删除再添加(更新value)
self.items[index].remove(item)
break
self.items[index].append((key, value))
return True
def get(self, key):
index = self.hash(key)
if self.items[index]:
for item in self.items[index]:
if self.equals(key, item[0]):
return item[1]
# 找不到key,则抛出KeyError异常
raise KeyError
def __setitem__(self, key, value):
"""支持以 myhash[1] = 30000 方式添加"""
return self.insert(key, value)
def __getitem__(self, key):
"""支持以 myhash[1] 方式读取"""
return self.get(key)
myhash = MyHash()
myhash[1] = 30000
myhash.insert(2, 2100)
print myhash.get(1)
myhash.insert(1, 3)
print myhash.get(2)
print myhash.get(1)
print myhash[1]
以上实现仅支持key为int类型的情况,若要支持其他类型的key,需重新实现hash方法及equals方法
仅实现了插入、读取方法,其他方法可以按照python中dict的接口方法再进行添加
实现了_setitem_、 _getitem_方法,使我们的对象也可像dict一样进行添加、读取
题目:
使用任何内建的哈希表库设计一个哈希映射(HashMap)。
实现 MyHashMap 类:
MyHashMap() 用空映射初始化对象
void put(int key, int value) 向 HashMap 插入一个键值对 (key, value) 。如果 key 已经存在于映射中,则更新其对应的值 value 。
int get(int key) 返回特定的 key 所映射的 value ;如果映射中不包含 key 的映射,返回 -1 。
void remove(key) 如果映射中存在 key 的映射,则移除 key 和它所对应的 value 。
解答:
1, 超大数组法
class MyHashMap(object):
def __init__(self):
self.map = [-1] * 1000001
def put(self, key, value):
self.map[key] = value
def get(self, key):
return self.map[key]
def remove(self, key):
self.map[key] = -1
时间复杂度(O(1))
空间复杂度(数据范围)
2,拉链法(
定义一个比较小的数组,然后使用hash方法来把求出key应该出现在数组的位置, 但不同的key在求完hash后,可能碰撞,所以数组并不直接保存元素,而是每个位置都指向一条链表,用户储存元素)
查找一个key: 1,求hash在数组中的位置 2,在链表中遍历找key
不定长的拉链数组是说拉链会根据分桶中的 key 动态增长,更类似于真正的链表。
分桶数一般取质数,这是因为经验上来说,质数个的分桶能让数据更加分散到各个桶中。下面的代码中把分桶数去了 1009,是因为 1009 是大于 1000 的第一个质数。
优点:节省内存,不用预知数据范围;
缺点:在链表中查找元素需要遍历。
class MyHashMap:
def __init__(self):
self.buckets = 1009
self.table = [[] for _ in range(self.buckets)]
def hash(self, key):
return key % self.buckets
def put(self, key: int, value: int) -> None:
hashkey = self.hash(key)
for item in self.table[hashkey]:
if item[0] == key:
item[1] = value
return
self.table[hashkey].append([key, value])
def get(self, key: int) -> int:
hashkey = self.hash(key)
for item in self.table[hashkey]:
if item[0] == key:
return item[1]
return -1
def remove(self, key: int) -> None:
hashkey = self.hash(key)
for i, item in enumerate(self.table[hashkey]):
if item[0] == key:
self.table[hashkey].pop(i)
return
三 总结
从HashMap的实现来看,我们总结拉链发的实现步骤如下:
1. 计算 key 的 hashValue
2. 根据 hashValue 值定位到 table[hashIndex] 。( table[hashIndex] 是一条链表Node)
3. 若 table[hashValue] 为空则直接插入,不然则添加到链表末尾