数据结构与算法----复习Part 11 (哈希表)

本系列是算法通关手册LeeCode的学习笔记

算法通关手册(LeetCode) | 算法通关手册(LeetCode) (itcharge.cn)

目录

一,哈希表(HashTable)

二,哈希函数(Hash Function)

2.1 直接定址法

2.2 除留余数法

2.3 平方取中法

2.4基数转换法

三,哈希冲突(Hash Collision)

3.1 开放地址法

​编辑

3.2 链地址法

四,例题


本系列为自用笔记,如有版权问题,请私聊我删除。

一,哈希表(HashTable)

        也叫做散列表,根据关键码值 (Key Value)直接进行访问的数据结构。

        哈希表通过【键 key】和【映射函数 Hash(key)】计算出相应的【值 value】,把关键码映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做【哈希函数】,存放记录的数组叫做【哈希表】

        哈希表的关键思想时使用哈希函数,将键 key 映射到对应表的某个区块中。有以下两部分:

        向哈希表中插入一个关键码值:哈希函数决定该关键字的对应值应该存放到表中的哪个区块,并将对应值存放到该区块中。

        在哈希表中搜索一个关键码值:使用相同的哈希函数从哈希表中查找对应的区块,并在特定的区块搜索该关键字对应的值。

       

        在上图例子中,使用 value = Hash(key) = key // 1000 作为哈希函数。

        向哈希表中插入一个关键码值:通过哈希函数解析关键字,并将对应值存放到该区块中。

                0138 通过哈希函数 Hash(key) = 0138 // 1000 = 0,得出应将 0138 分配到 0 所在的区

        块中。         

        在哈希表中搜索一个关键码值:通过哈希函数解析关键字,并在特定的区块搜索该关键

        字对应的值。

                查找 2321,通过哈希函数,得出 2321 应该在 2 所对应的区块中,然后从 2 对应的区块

        中继续搜索,并在 2 对应的区块中成功找到了 2321;

                查找 3241,通过哈希函数,得出 3241 应该在 3 所对应的区块中,然后从 3 对应的区块

        中继续搜索,没有找到对应值,则说明 3241 不在哈希表中。

二,哈希函数(Hash Function)

        将哈希表中元素的关键值映射为元素存储位置的函数。

        哈希函数是哈希表中最重要的部分,应满足:

        1,哈希函数应易于计算,并且尽量使计算出来的索引值均匀分布;

        2,哈希函数计算得到的哈希值是一个固定长度的输出值;

        3,如果 Hash(key1) == Hash(key2) ,那么 key1、key2 可能相等也可能不相等。

2.1 直接定址法

        取关键字本身 / 关键字的某个线性函数值作为哈希地址。

                Hash(key) = key 或 Hask(key) = a * key + b

        这种计算方式最简单且不会产生冲突。

2.2 除留余数法

        假设哈希表的表长为 m,取一个不大于 m 但接近或等于 m 的质数 p,利用取模运算,将关键字转换为哈希地址。

                Hash(key) = key mod p

        其中 p 为不大于 m 的质数。

2.3 平方取中法

        先通过求关键字平方值的方式扩大相近数之间的差别,然后根据表长取关键字平方值的中间几位数为哈希地址。

                Hash(key) = (key * key) // 100 mod 1000

        先计算平方,去除末尾 2 位数,再取中间 3 位数作为哈希地址。

        这种方法因为关键字平方值的中间几位数和原关键字的每一位数都相关,所以产生的哈希地址也比较均匀,有利于减少冲突的发生。

2.4基数转换法

        将关键字看成另一种进制的数,再转回原来进制的数,然后选其中几位作为哈希地址。

三,哈希冲突(Hash Collision)

        不同关键字通过同一个哈希函数可以得到同一哈希地址:

                key1 != key2

                Hash(key1) == Hash(key2)

        称为哈希冲突

        常用解决哈希冲突的方法主要是两类:【开放地址法(Open Addressing)】和【链地址法(Chaining)】

3.1 开放地址法

        指的是将哈希表中的【空地址】向处理冲突开放。当哈希表未满时,处理冲突时,需要尝试另外的单元,直到找到空的单元为止。

        发生冲突时,开放地址法按照

                H( i ) = ( Hash( key ) + F( i ) )mod m , i = 1, 2, 3...n ( n <= m - 1 )

        的方法求得后继哈希地址。

        H( i ) 是在处理冲突中得到的地址序列。即在第一个冲突 ( i = 1 )是经过处理得到了一个新地址

H( 1 ),如果在 H( 1 ) 处仍然冲突,经过处理得到 H( 2 ) ,直到求得的 H( n ) 不再冲突。

        Hash( key ) 是哈希函数,m 是表长,对 m 取余保证得到的下一个地址一定落在哈希表中。

        F( i ) 是冲突解决方法,取法有以下几种:

                线性探测法: F( i ) = 1, 2, 3,,,, m - 1.

                二次探测法:F( i ) = 1 ** 2, -1 ** 2, 2 ** 2, -2 ** 2,,,

                伪随机数序列:F( i ) = 伪随机数序列

        举个例子说说明一下如何用以上三种冲突解决方法处理冲突,并得到新地址 H(i)。例如,在长度为 11 的哈希表中已经填有关键字分别为 28、49、18 的记录

        哈希函数为 Hash(key)=key mod  11。现在将插入关键字为 38 的新纪录。根据哈希函数得到的哈希地址为 55,产生冲突。接下来分别使用这三种冲突解决方法处理冲突。

  • 使用线性探测法:得到下一个地址 H(1)=(5+1)mod  11=6,仍然冲突;继续求出 H(2)=(5+2)mod  11=7,仍然冲突;继续求出 H(3)=(5+3)mod  11=8,8 对应的地址为空,处理冲突过程结束,记录填入哈希表中序号为 8 的位置。
  • 使用二次探测法:得到下一个地址 H(1)=(5+1×1)mod  11=6,仍然冲突;继续求出 H(2)=(5−1×1)mod  11=4,4 对应的地址为空,处理冲突过程结束,记录填入哈希表中序号为 4 的位置。
  • 使用伪随机数序列:假设伪随机数为 9,则得到下一个地址H(1)=(9+5)mod  11=3,3 对应的地址为空,处理冲突过程结束,记录填入哈希表中序号为 3 的位置。

3.2 链地址法

        将具有相同哈希地址的元素(或记录)存储在同一个线性链表中,是一种更加常用的哈希冲突解决方法。

        假设哈希函数产生哈希地址的区间为 [ 0, m - 1 ],哈希表的表长为 m,则可以将哈希表定义为一个有 m 个头节点组成的链表指针数组 T

                这样在插入关键字的时候,我们只需要通过哈希函数计算出对应的哈希地址,然后将其

        以链表节点的形式插入到以 T[ i ] 为头节点的单链表中。如果每次插入的位置为表头,则插入

        的时间复杂度为 O( 1 ).

                而在查询关键字的时候,我们只需要通过哈希函数计算出对应的哈希地址 i,然后将对应

        位置上的链表整个扫描一遍,比较链表中的每个链节点的键值与查询的键值是否一致。

        相对于开放地址法,采用链地址法处理冲突要多占用一些存储空间,但它可以减少在进行插入和查找具有相同哈希地址关键字的操作过程中的平均查找长度。

四,例题

705. 设计哈希集合

class MyHashSet:

    def __init__(self):
        self.buckets = 1009
        self.table = [[] for _ in range(self.buckets)]
    
    def hash(self, key):
        return key % self.buckets

    def add(self, key: int) -> None:
        hashkey = self.hash(key)
        if key in self.table[hashkey]:
            return 
        self.table[hashkey].append(key)

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


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

706. 设计哈希映射

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

        设计哈希映射时,在遍历 table 中,使用 item 作为存储的元素

        并以此判断 self.table[hashkey] 中是否有 key 为关键字的元素

217. 存在重复元素

class Solution:
    def containsDuplicate(self, nums: List[int]) -> bool:
        res = dict()
        for num in nums:
            if num in res:
                return True
            res[num] = num
        return False

219. 存在重复元素 II

class Solution:
    def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool:
        res = dict()
        for i, num in enumerate(nums):
            if num in res:
                if i - res[num] <= k:
                    return True
            res[num] = i
        return False
        

1941. 检查是否所有字符出现次数相同

class Solution:
    def areOccurrencesEqual(self, s: str) -> bool:
        res = dict()
        for i in s:
            if i in res:
                res[i] += 1
            else:
                res[i] = 1
        flag = int(res[s[0]])
        for item in res.values():
            if item != flag:
                return False
        return True

36. 有效的数独

class Solution:
    def isValidSudoku(self, board: List[List[str]]) -> bool:
        row = [[0] * 9 for _ in range(9)]
        col = [[0] * 9 for _ in range(9)]
        block = [[0] * 9 for _ in range(9)]

        for i in range(9):
            for j in range(9):
                if board[i][j] != '.':
                    num = int(board[i][j]) - 1
                    b = (i // 3) * 3 + j // 3
                    if row[i][num] or col[j][num] or block[b][num]:
                        return False
                    row[i][num] = col[j][num] = block[b][num] = 1
        return True

算法通关手册(LeetCode) | 算法通关手册(LeetCode)

原文内容在这里,如有侵权,请联系我删除。

  • 16
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值