Python 算法之 设计哈希集合
哈希集合🎏
到底 什么是哈希集合🤔
- 哈希集合 『hash set』,是一种通过哈希值来定位存储位置的数据结构,只存储Key本身,没有对应值,通过哈希把 key 转成哈希值作为数据存储的位置索引,而该位置上存储内容为key本身
- 哈希集合 不能存储重复元素 key,因为名称就已经说的很清楚了,它是个集合
给出题目🎎
不使用任何内建的哈希表库设计一个哈希集合(HashSet)
实现 MyHashSet 类:
- add(key) 方法: 向哈希集合中插入值 key
- remove(key) 方法: 将给定值 key 从哈希集合中删除,如果哈希集合中没有这个值,则什么都不做
- contains(key) -> bool: 返回哈希集合中是否存在这个值 key
输入:
[“MyHashSet”, “add”, “add”, “contains”, “contains”, “add”, “contains”, “remove”, “contains”]
[[], [1], [2], [1], [3], [2], [2], [2], [2]]
输出:
[null, null, null, true, false, null, true, null, false]
思路:😆
为对于 哈希集合来说(hashset),只存储Key本身,没有对应值,那只用判断这个key 是否存在即可,如果存在的返回True,反之返回False。
根据这个思路,有以下几种解题方法
方法一:简单大数组
将数组元素设计为 布尔型 bool,key 为数组的索引,取值为Ture代表key存在,为Fasle时则代表数据不存在
特点
- 编写简单,容易理解
- 查找和删除的速度快,根据索引只用访问一次
- 最大的一个缺陷,就是需要一个很大的数组去存储元素,占用了非常大的空间,当数据范围很大的时候,这个方法会出现问题,当数据范围较小的时候,又会因为占用了很大的空间导致性价比极低
- 需要知道数据的范围大小
class MyHashSet:
def __init__(self):
self.hashset = [False] * 1000000 # 新建了长度为 1000000 的数组
def add(self, key: int):
"""
添加值
:rtype: None
"""
self.hashset[key] = True
def remove(self, key: int):
"""
删除值
:rtype: None
"""
self.hashset[key] = False
def contains(self, key) -> bool:
"""
是否包含
:return: 布尔类型
"""
return self.hashset[key]
执行效率:
- 时间复杂度: O(1)
- 空间复杂度: O(自定数据范围)
方法二:链地址法
通过将 key 求出哈希值 后可以取得存储位置,但不同的 key 求出的哈希值可能会出现一样的情况,这就照成了冲突,所以不能直接存储元素,而是将每个位置指向一个链表用于存储元素。
不定长拉链数组
特点
- 节约内存空间
- 通过哈希值获取数据存储位置后,需要在链表中遍历查找元素
- 不需要知道数据的范围大小
class MyHashSet:
def __init__(self):
self.BASE = 967 # 将其设置为质数,尽量减少冲突
self.hashset = [[] for _ in range(self.BASE)]
def hash(self, key):
return key % self.BASE # 使用求余来代替hash函数
def add(self, key: int):
"""
添加值
:rtype: None
"""
hashkey = self.hash(key)
if key not in self.hashset[hashkey]:
self.hashset[hashkey].append(key)
def remove(self, key: int):
"""
删除值
:rtype: None
"""
hashkey = self.hash(key)
if key in self.hashset[hashkey]:
self.hashset[hashkey].remove(key)
def contains(self, key) -> bool:
"""
是否包含
:return: 布尔类型
"""
hashkey = self.hash(key)
return key in self.hashset[hashkey]
执行效率:
- 时间复杂度: O(1)
- 空间复杂度: O(自定数据范围)
定长拉链数组
特点
- 需要知道数据的范围大小
class MyHashSet:
def __init__(self):
self.BASE = 1669 # 将其设置为质数,尽量减少冲突
self.hashset = [False for _ in range(self.BASE)]
def hash(self, key):
return key % self.BASE # 使用求余来代替hash函数
def index(self, key): # 获取第二维度位置
return key // self.BASE
def add(self, key: int):
"""
添加值
:rtype: None
"""
hashkey, index = self.hash(key), self.index(key)
if not self.hashset[hashkey]: # 只有需要时才会构建第二维度数组
self.hashset[hashkey] = ([False]*(self.BASE+1)) # +1 是因为 679//679 时不为0
self.hashset[hashkey][index] = True
def remove(self, key: int):
"""
删除值
:rtype: None
"""
hashkey, index = self.hash(key), self.index(key)
if self.hashset[hashkey]:
self.hashset[hashkey][index] = False
def contains(self, key) -> bool:
"""
是否包含
:return: 布尔类型
"""
hashkey, index = self.hash(key), self.index(key)
return self.hashset[hashkey] and self.hashset[hashkey][index]
方法三:散列表
哈希表『hash table』,又叫散列表,根据输入关键字 key 来进行访问的数据结构,通过哈希把 key 转换成哈希值作为数据存储的位置索引,从而确定 值 (value) 存储在什么地方
散列表
- 散列表总是会将同样的输入 (key) 映射到相同的索引得到相同的值 (value),不同的输入会输出不同的值
- 散列表的查找、插入和删除速度都特别快
- Python 提供的 字典 即为散列表的实现
需要注意的是,题目要求我们 不使用任何内建的哈希表库,那么这种方法是违规的,这里作为参考
class MyHashSet:
def __init__(self):
self.hashset = {}
def add(self, key: int):
"""
添加值
:rtype: None
"""
self.hashset[key] = True
def remove(self, key: int):
"""
删除值
:rtype: None
"""
self.hashset[key] = False
def contains(self, key) -> bool:
"""
是否包含
:return: 布尔类型
"""
return self.hashset[key] if key in self.hashset else False
执行效率:
- 时间复杂度: O(1)
- 空间复杂度: O(n)
使用集合
利用集合的去重性
class MyHashSet:
def __init__(self):
self.hashset = set() # 新建一个集合
def add(self, key: int):
"""
添加值
:rtype: None
"""
self.hashset.add(key) # 集合天生去重
def remove(self, key: int):
"""
删除值
:rtype: None
"""
if key in self.hashset:
self.hashset.remove(key)
def contains(self, key) -> bool:
"""
是否包含
:return: 布尔类型
"""
return True if key in self.hashset else False
参考资料
- 题目出之力扣:设计哈希集合 leetcode 705
- 感谢题解里 负雪明烛 大佬所带给我的启发
由衷感谢💖