哈希表(散列表)基础概念与经典题目(Leetcode题解-Python语言)之上——原理与设计

哈希表Hash table,也叫散列表),是根据键(Key)而直接访问数据在内存中的储存位置(又叫做存储桶Buckets)的数据结构。也就是说,它通过计算一个关于键值的函数(哈希函数Hash function,也叫散列函数),将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度,存放记录的数组就叫做哈希表。最直观的例子,我们的手机通讯录中,如果王某某的电话号码是12345678910,我们只需要记个王某某:12345678910,以后直接找王某某就可以找到他的电话号码了。哈希表可以在O(1)时间内进行读写操作(插入与搜索),就是最大的优势。(leetcode对应章节)

哈希表的关键思想是使用哈希函数将键映射到存储桶

1.当我们插入一个新的键时,哈希函数将决定该键应该分配到哪个桶中,并将该键存储在相应的桶中;
2.当我们搜索一个键时,哈希表将使用相同的哈希函数来查找对应的桶,并只在特定的桶中进行搜索。

如果两个不同的键经过哈希函数映射之后,得到的值相同,这叫做哈希冲突或者碰撞(collision),解决方法后面再讲。

如何设计一个哈希表呢?最重要的肯定是哈希函数,完美的哈希函数是键与桶一 一对应,没有任何冲突或者空间浪费的,然而实际基本不可能达到。哈希表又分为两种:哈希集合哈希映射,所以设计题就分为设计哈希集合和设计哈希映射。

705. 设计哈希集合

集合是用来存储非重复值的数据结构,操作就三种:插入、查找(是否存在)、删除。做设计题必须留意键key的取值范围,此处为0到10的六次方,因此会有对于桶数(数组的长度)与桶大小(数组中一个格子的大小)的权衡。

最极端的,开辟一个超大数组(桶数为10的六次方、桶大小为1),方案如下:

class MyHashSet:

    def __init__(self):
        """
        Initialize your data structure here.
        """
        self.HashSet = [False] * 1000001

    def add(self, key: int) -> None:
        self.HashSet[key] = True

    def remove(self, key: int) -> None:
        self.HashSet[key] = False
        
    def contains(self, key: int) -> bool:
        """
        Returns true if this set contains the specified element
        """
        return self.HashSet[key]

初始化数组每一格都是False,插入就是对应格变为True,查找就是对应格的值(True or False),删除就是对应格变为False。

如果希望平衡桶数与桶大小相同,可以得到以下方案:

class MyHashSet:

    def __init__(self):
        self.table = [[0] * 1000 for _ in range(1001)] # 注意是1001

    def hash(self, key):
        return key // 1000, key % 1000  # 获取key在数组中的位置索引
    
    def add(self, key):
        hashkey, hashpos = self.hash(key)  # 获取key在数组中的位置索引
        self.table[hashkey][hashpos] = 1
        
    def remove(self, key):
        hashkey, hashpos = self.hash(key)
        self.table[hashkey][hashpos] = 0

    def contains(self, key):
        hashkey, hashpos = self.hash(key)
        return (self.table[hashkey] != []) and (self.table[hashkey][hashpos] == 1)

显然,这里我们开辟了一个长度 length 为1000的数组,哈希函数为取整除,冲突的解决方法是让一个格子不止存放一个 key,而是用数组把所有可能冲突的 key 都存放进来,只需要桶数(1000) * 桶大小(1001)的结果大于10的六次方即可。

hashkey是key经过哈希函数(取整除)映射后的值,可能会重复(冲突),用于确定key存放在哪个桶;

hashpos是key经过取余数后的值,可知不同的key得到相同的整除值后,它的余数一定是不同的,所以可以确定key存放在某一个桶中的哪个位置。

706. 设计哈希映射

映射是用来存储 (key, value) 键值对的数据结构,设计思路与集合类似,如下:

class MyHashMap(object):

    def __init__(self):
        self.map = [[-1] * 1000 for _ in range(1001)] # 注意是1001
        
    def hash(self, key):
        return key // 1000, key % 1000  # 获取key在数组中的位置索引
        
    def put(self, key, value):
        hashkey, hashpos = self.hash(key)
        self.map[hashkey][hashpos] = value

    def get(self, key):
        hashkey, hashpos = self.hash(key)
        return self.map[hashkey][hashpos]

    def remove(self, key):
        hashkey, hashpos  = self.hash(key)
        self.map[hashkey][hashpos] = -1

这里我使用的哈希函数还是取整除,桶数还是1001,桶大小还是1000。

注意:之所以是 range(1001) 而不是 1000 的原因是10的六次方除以 1000 会得到 1000,而 range(1000) 最大是999,导致 1000 溢出。

下面是更好的方法!!

705. 设计哈希集合

class MyHashSet:

    def __init__(self):
        self.buckets = 1001
        self.table = [[] for _ in range(self.buckets)]
        
    def hash(self, key):
        return key % self.buckets
    
    def add(self, key: int) -> None:
        hash_key = self.hash(key)
        if key in self.table[hash_key]:
            return
        self.table[hash_key].append(key)

    def remove(self, key: int) -> None:
        hash_key = self.hash(key)
        if key not in self.table[hash_key]:
            return
        self.table[hash_key].remove(key)

    def contains(self, key: int) -> bool:
        hash_key = self.hash(key)
        return key in self.table[hash_key]

只需要设计哈希函数找到桶的位置即可,让桶作为一个空的列表,里面的元素动态增加。

706. 设计哈希映射

class MyHashMap:

    def __init__(self):
        self.buckets = 1001
        self.table = [[] for _ in range(self.buckets)]

    def hash(self, key):
        return key % self.buckets

    def put(self, key: int, value: int) -> None:
        hash_key = self.hash(key)
        for item in self.table[hash_key]:
            if key == item[0]:
                item[1] = value
                return
        self.table[hash_key].append([key, value])

    def get(self, key: int) -> int:
        hash_key = self.hash(key)
        for item in self.table[hash_key]:
            if key == item[0]:
                return item[1]
        return -1

    def remove(self, key: int) -> None:
        hash_key = self.hash(key)
        for i, item in enumerate(self.table[hash_key]):
            if key == item[0]:
                self.table[hash_key].pop(i)
                return

同理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值