前情提要:

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] 为空则直接插入,不然则添加到链表末尾