Python 算法之 设计哈希集合

本文介绍如何从零开始设计实现一个哈希集合,包括三种方法:简单大数组、链地址法和散列表。每种方法均有详细的实现步骤及优缺点分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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
  • 感谢题解里 负雪明烛 大佬所带给我的启发

由衷感谢💖


相关博客😏

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值