数据结构与算法(一)哈希表(Hash Table)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/weixin_39538889/article/details/85720714

数据结构与算法(一)哈希表(Hash Table)


哈希表是一种 数据结构,其中数据以关联方式存储( 以key, value格式)。Key或index是 唯一的。这种存储方式使得过后更容易找到数据。

哈希表将数据存储为数组(array)格式。它使用散列函数(hash function)生成插槽(slot)或索引来存储和插入任何元素或值。

我们将创建自己的散列函数和哈希表。但在此之前,让我们看一下内置的Python字典和Python列表。

Python字典

  • 这是一种Python数据类型,与其他编程语言中的“关联数组”相同。
  • 数据存储在键和值得映射中。
  • 从Python 字典中检索数据很容易。

Example:

country_code = {'25': 'USA' ,'20': 'India', '10': 'Nepal'}
 
print country_code['10'] # Output: Nepal
print country_code['20'] # Output: India

Python列表

  • 列表是Python中的另一种数据类型。
  • 数据可以存储为元组。
  • 它不像python字典那样容易检索项目。
  • 我们需要使用循环来搜索列表中的任何项目。
  • 检索数据非常耗时。

Example:

country_code = [('25', 'USA'), ('20', 'India'), ('10', 'Nepal')]
 
def insert(item_list, key, value):
    item_list.append((key, value))
 
def search(item_list, key):
    for item in item_list:
        if item[0] == key:
            return item[1]
    
print (search(country_code, '20')) # Output: India
print (insert(country_code, '100')) # Output: None, python returns 'None' by default if the searched item is not found in the list

简单实现哈希表

这是一个非常简单的哈希表和散列函数的实现。

散列函数/散列

通过散列函数为任何“key”值生成插槽或索引。完美散列或完美散列函数是为每个key值分配唯一插槽的函数。有时,可能存在散列函数为多个键值生成相同索引的情况。我们可以通过拓展散列表的大小来改善散列函数的表现。

首先,让我们创建一个大小为10的哈希表,其中包含数据为空。

hash_table = [None] * 10
print (hash_table) 
# Output: 
# [None, None, None, None, None, None, None, None, None, None]

下面是一个简单的哈希函数,它返回哈希表长度的模。在我们的例子中,哈希表的长度是10。

模数运算符(%)用在散列函数中。模数运算符%(modulo)从第一个参数除以第二个参数得到余数。

def hashing_func(key):
    return key % len(hash_table)
 
print (hashing_func(10)) # Output: 0
print (hashing_func(20)) # Output: 0
print (hashing_func(25)) # Output: 5

将数据插入哈希表

这是将数据/值插入哈希表的简单实现。我们首先使用散列函数生成一个槽/索引并将给定值插入到该槽​​中。

def insert(hash_table, key, value):
    hash_key = hashing_func(key)
    hash_table[hash_key] = value 
 
insert(hash_table, 10, 'Nepal')
print (hash_table)
# Output: 
# ['Nepal', None, None, None, None, None, None, None, None, None]
 
insert(hash_table, 25, 'USA')
print (hash_table)
# Output: 
# ['Nepal', None, None, None, None, 'USA', None, None, None, None]

碰撞(Collision)
当两个值通过计算得到的相同的索引时,就产生了冲突,即散列函数为多个项生成相同的索引号。如果不采取措施来避免碰撞,那么每当碰撞发生时,插槽中的前一项就将被新的项所取代

在上面的示例代码中,我们分别使用‘’10‘’和‘’25‘’插入了项目Nepal和USA。如果我们尝试插入带有‘’20‘’的新项目,则发生碰撞,因为我们的散列函数将为‘’20‘’生成索引‘’0‘’.但是,散列表中的‘’0‘’已经被分配给“Nepal”。

insert(hash_table, 20, 'India')
print (hash_table)
# Output: 
# ['India', None, None, None, None, 'USA', None, None, None, None]

这里我们可以看到,索引“0”的位置已经被新的“India”所取代,因为键值“10”和“20”通过散列函数得到的结果相同都为“0”

碰撞解决方案

通常有两种方法来解决碰撞:

1.线性探测(Linear Probing)
2.链接(Chaining)

1.线性探测

解决冲突的一种方法是在发生碰撞时找到另一个空白的索引并将该项存储在这个空白的索引中。搜索打开的索引从发生碰撞的索引开始。它顺序移动通过索引,直到遇到空的索引。移动是循环的。它可以从第一个索引开始搜索空索引。这种顺序搜索称为线性探测。

这里有一个线性探测的拓展阅读,包括代码实现:http://www.cnblogs.com/hanahimi/p/4765265.html

2.链接

解决冲突的另一种方法是链接。这允许在同一槽/索引中存在多个项目。这可以在单个槽中创建集合。发生碰撞时,使用链接机制将项目存储在同一插槽中。

在Python中实现Chaining时,我们首先将哈希表创建为嵌套列表(lists inside a list)。

hash_table = [[] for _ in range(10)]
print (hash_table)
# Output: 
# [[], [], [], [], [], [], [], [], [], []]

散列函数同上文

def hashing_func(key):
    return key % len(hash_table)
 
print (hashing_func(10)) # Output: 0
print (hashing_func(20)) # Output: 0
print (hashing_func(25)) # Output: 5

我们使用append() 函数来将键和值的数据对放入哈希表中

def insert(hash_table, key, value):
    hash_key = hashing_func(key)
    hash_table[hash_key].append(value)
 
insert(hash_table, 10, 'Nepal')
print (hash_table)
# Output: 
# [['Nepal'], [], [], [], [], [], [], [], [], []]
 
insert(hash_table, 25, 'USA')
print (hash_table)
# Output: 
# [['Nepal'], [], [], [], [], ['USA'], [], [], [], []]
 
insert(hash_table, 20, 'India')
print (hash_table)
# Output: 
# [['Nepal', 'India'], [], [], [], [], ['USA'], [], [], [], []]

标准实现

下面介绍使用Python进行Hash Table的更标准实现。我们创建了三个不同的函数来插入,搜索和删除哈希表中的项目。

Python的内置“哈希”函数用于创建任何键的哈希值。此函数很有用,因为它为字符串和整数键创建整数哈希值。整数的哈希值将与自己相同,即hash(10)将为10,hash(20)将为20,依此类推。

在下面的代码中,请注意使用10和“10”时输出的差异。10(不带引号)被视为整数,“10”(带引号)被视为字符串。

hash_key = hash('xyz')
print (hash_key) # Output: -5999452984703080694
hash_key = hash('10')
print (hash_key) # Output: 6272037681056609
hash_key = hash(10)
print (hash_key) # Output: 10
hash_key = hash('20')
print (hash_key) # Output: 6400038450057764
hash_key = hash(10)
print (hash_key) # Output: 20
 
hash_key = hash('10') % len(hash_table)
print (hash_key) # Output: 9
hash_key = hash('20') % len(hash_table)
print (hash_key) # Output: 4
hash_key = hash('25') % len(hash_table)
print (hash_key) # Output: 1

hash_key = hash(10) % len(hash_table)
print (hash_key) # Output: 0
hash_key = hash(20) % len(hash_table)
print (hash_key) # Output: 0
hash_key = hash(25) % len(hash_table)
print (hash_key) # Output: 5

将数据插入哈希表

让我们将哈希表list中的的每个list命名为“bucket”。

在将新元素插入哈希表时,我们首先搜索密钥是否已存在于哈希表中。

  • 如果密钥已经存在于哈希表中,那么我们用新的值更新其值。
  • 否则,我们在哈希表中插入一个新的键值对。
def insert(hash_table, key, value):
    hash_key = hash(key) % len(hash_table)
    key_exists = False
    bucket = hash_table[hash_key]    
    for i, kv in enumerate(bucket):
        k, v = kv
        if key == k:
            key_exists = True 
            break
    if key_exists:
        bucket[i] = ((key, value))
    else:
        bucket.append((key, value))
 
 
insert(hash_table, 10, 'Nepal')
insert(hash_table, 25, 'USA')
insert(hash_table, 20, 'India')
print (hash_table)
# Output:
# [[(10, 'Nepal'), (20, 'India')], [], [], [], [], [(25, 'USA')], [], [], [], []]

从哈希表中搜索数据
在哈希表中搜索任何键时,我们必须遍历每个单独的子列表。

def search(hash_table, key):
    hash_key = hash(key) % len(hash_table)    
    bucket = hash_table[hash_key]
    for i, kv in enumerate(bucket):
        k, v = kv
        if key == k:
            return v
 
print (search(hash_table, 10)) # Output: Nepal
print (search(hash_table, 20)) # Output: India
print (search(hash_table, 30)) # Output: None

从哈希表中删除数据

从哈希表中删除元素(键值对)有点类似于插入元素。循环几乎相同。

从哈希表中删除任何现有元素时,我们首先搜索密钥是否已存在于哈希表中。

  • 如果密钥在哈希表中存在(找到),那么我们只需删除它。我们从哈希表中删除该特定键值对。
  • 否则,不进行任何操作。我们可以简单地打印一条消息,说明在哈希表中找不到密钥。
def delete(hash_table, key):
    hash_key = hash(key) % len(hash_table)    
    key_exists = False
    bucket = hash_table[hash_key]
    for i, kv in enumerate(bucket):
        k, v = kv 
        if key == k:
            key_exists = True 
            break
    if key_exists:
        del bucket[i]
        print ('Key {} deleted'.format(key))
    else:
        print ('Key {} not found'.format(key))
 
 
delete(hash_table, 100)
print (hash_table)
# Output:
# Key 100 not found
# [[(10, 'Nepal'), (20, 'India')], [], [], [], [], [(25, 'USA')], [], [], [], []]
 
delete(hash_table, 10)
print (hash_table)
# Output:
# Key 10 deleted
# [[(20, 'India')], [], [], [], [], [(25, 'USA')], [], [], [], []]

本文中理论部分翻译并修改自Mukesh Chapagain博客,原文地址:http://blog.chapagain.com.np/hash-table-implementation-in-python-data-structures-algorithms/

LeetCode思想实践:

#1 Two Sum

https://leetcode.com/problems/two-sum/

原题的意思大致就是给你一个包含整形的数组目标,如果其中两个整数求和取得的结果与给定的目标一致,则返回两个整数对应的索引

Given nums = [2, 7, 11, 15], target = 9,

Because nums[0] + nums[1] = 2 + 7 = 9,
return [0, 1].

这道题简单解法,如果数据量不大的话,通过循环判断求和结果然后使用索引就好了,数据量大就gg了

class Solution:
    def twoSum(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        for i in range(len(nums)-1):
          for j in range(i+1,len(nums)):
            if target == nums[i] + nums[j] :
              return [i,j]

但是这道题我们要尝试使用哈希表思想实现

既然我们想最后得到索引值,那么我们就把它设定为我们数据对中的value值,给定数组中的整数即为key值,在哈希表中,直接通过key值找value值,输出结果

今天遇到的最大问题就是太长时间不写类了,很多地方不知道具体怎么写,好在摸索出了结果,过程很艰难,写的不好大家别笑话,毕竟我们是学习数据结构lol

class Solution:
    def twoSum(self, nums, target):
      self.nums = nums
      self.target =target
      hash_table = [[] for _ in nums]
      for i in range(len(nums)):
        Solution.insert(hash_table,nums[i],i)
      for i in range(len(nums)-1):
        for j in range(i+1,len(nums)):
          if target == nums[i] + nums[j] :
            return [Solution.search(hash_table,nums[i]) , Solution.search(hash_table,nums[j])]

    def insert(hash_table, key, value):
      hash_key = hash(key) % len(hash_table)
      key_exists = False
      bucket = hash_table[hash_key]    
      for i, kv in enumerate(bucket):
          k, v = kv
          if key == k:
              key_exists = True 
              break
      if key_exists:
          bucket[i] = ((key, value))
      else:
          bucket.append((key, value))

    def search(hash_table,key):
        hash_key = hash(key) % len(hash_table)    
        bucket = hash_table[hash_key]
        for i, kv in enumerate(bucket):
            k, v = kv
            if key == k:
                return v

第一次尝试,完整的实现了放和拿的功能,检验出现错误,如果两个数相同,不能返回正确的索引

例如[3,3],6
3+3=6,输出的结果为[1,1],实际应为[0,1]

这次提交带给我一次反思,我们前面说过,python中的字典就是用哈希表实现的,我们为什么不直接使用字典来帮我们解决问题呢,往里放就完了呗

class Solution:
    def twoSum(self, nums, target):
        dict = {}
        for i in nums:
            second = target - i
            if second in dict:
                return [dict[second],nums.index(i)]
            dict[i] = nums.index(i)

老问题!!!不能拿数找索引,反过来拿索引找数!!!

过验结果:

class Solution:
    def twoSum(self, nums, target):
        dict = {}
        for i in range(len(nums)):
            second = target - nums[i]
            if second in dict:
                return [dict[second],i]
            dict[nums[i]] = i

#202 Happy Number

https://leetcode.com/problems/happy-number/

什么是快乐数?如果把数字拆分成多个个位数,他们的平方和为1,即为快乐数

例子:

Input: 19
Output: true
Explanation:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1

所以一旦结果重复了,就无限循环了,直接false
这道题不像第一道题,只有value没有key,我就想不到怎么个放法了,那就不用成对放了,拿个list开心一下

class Solution:
    def isHappy(self, n):
        """
        :type n: int
        :rtype: bool
        """
        list1 = []
        while True:
            res = sum([int(a)**2 for a in str(n)])
            if res == 1:
                return True        
            elif res in list1:
                return False
            else:
                list1.append(res)
            n = res

题看着比较吓唬人,实际比第一题简单xD

在这里插入图片描述

今天的学习到这就结束了,自己的收获很大,也希望这篇博客对没学过哈希表的人有所帮助

看过的资料走一走:
1.用python实现哈希表 https://www.cnblogs.com/cjyfff/p/3536525.html
2.维基百科-散列函数 https://zh.wikipedia.org/wiki/散列函數
3.哈希表学习笔记 http://www.cnblogs.com/hanahimi/p/4765265.html
4.Hash Table implementation in Python [Data Structures & Algorithms] http://blog.chapagain.com.np/hash-table-implementation-in-python-data-structures-algorithms/
5.3_4 Hash表https://www.youtube.com/watch?v=HiNcWuHFaLg
虽然是用Java语言讲的,但是也可以看一下

下次见

猜你喜欢

转载自blog.csdn.net/weixin_39538889/article/details/85720714