【算法】哈希算法和哈希表

一、哈希算法

哈希算法是一种将任意长度的数据(也称为“消息”)转换为固定长度字符串(也称为“哈希值”或简称“哈希”)的数学函数或算法。这个固定长度的字符串是由输入数据通过一系列的运算得到的,并且具有一些重要的特性。

哈希算法的主要特性包括:

  1. 确定性:对于相同的输入,无论何时何地计算,得到的哈希值都是相同的。
  2. 不可逆:无法从哈希值反向推导出原始数据,也就是说,哈希算法是单向的。
  3. 敏感性:如果输入数据发生微小的变化,那么计算出的哈希值将会有很大的不同。并且哈希值应该均匀分布。
  4. 唯一性:理论上,对于不同的输入,很难得到相同的哈希值,也就是说,哈希冲突的概率应该是非常小的。
  5. 高效性:计算哈希值的过程应该是高效的。

哈希算法在许多领域都有广泛的应用,包括数据安全、数据压缩、数据检索等。例如,在密码学中,哈希算法被用来验证数据的完整性和身份;在数据压缩中,哈希算法被用来快速查找和替换重复的数据;在数据检索中,哈希算法被用来快速定位特定的数据。

常见的哈希算法包括MD5、SHA-1和SHA-256等。这些算法通过将输入数据分割成若干个等长或不等长的块,并对每个块进行一系列的位运算、移位运算、模运算、异或运算等,得到一个中间结果,称为一个“消息摘要”。最后,将所有的消息摘要进行组合或再次运算,得到最终的输出,称为一个“哈希值”。

二、哈希表

哈希表(Hash Table)是一种实现了键值对映射的数据结构,它使用哈希算法来计算应该将一个键存储在内部数组的哪个位置。哈希表通常用来实现关联数组和字典等高效的查找和插入数据结构。
哈希表中最重要的两个组件是:
- 哈希函数:哈希表依靠一个称为哈希函数的算法来将键映射到数组的索引位置。哈希函数的选择至关重要,因为它直接影响到哈希表的性能。
- 存储数组:一个用来实际存储数据的内部数组。
在使用哈希表时,经常需要处理哈希冲突,即两个不同的键可能被哈希到同一个位置。

处理哈希冲突的方法有好几种:
1. 链地址法:将所有哈希到同一个索引的元素链在一起存储。可以使用链表、双向链表或动态数组来存储同一个索引的元素。
2. 开放寻址法:一旦发生冲突,就在数组中探查其他的索引位置,直到找到一个空位。线性探查和二次探查是开放寻址法的两种常用策略。
3. 双重散列:使用多个哈希函数来计算索引位置。
哈希表的关键操作包括:
- 插入(Insert):添加键值对到哈希表。
- 删除(Delete):删除指定键的键值对。
- 搜索(Search/Find):寻找指定键的值。
哈希表的性能依赖于哈希函数的质量、处理哈希冲突的方法以及哈希表的负载因子(即已存储元素的数量与哈希表大小的比例)。理想情况下,哈希表的时间复杂度为 O(1),但在最坏的情况下(如所有元素都映射到同一个位置时)可能退化到 O(n)。

三、两者之间关系

哈希算法和哈希表是两个不同的概念,但它们之间有一定的关联。

哈希算法是一种将任意长度的数据转换为固定长度字符串的算法。

哈希表是一种数据结构,它使用哈希算法来将键映射到值。

- 哈希算法的应用:哈希表就是哈希算法的一个典型应用。一个好的哈希函数可以减少哈希表中键的冲突,这对于维护哈希表的高效运作至关重要。
- 哈希表中的关键要素:哈希表使用哈希算法来计算索引值,将键映射到哈希表的一个位置上。
- 配合策略以解决冲突:由于哈希表存储空间有限,即使是好的哈希算法也可能会导致不同键产生相同的哈希值,即发生冲突。因此,哈希表必须有一套策略(如链地址法或开放地址法)来解决这种冲突。

总之,哈希算法是构建和操作哈希表数据结构的基础。哈希表依赖于哈希算法来快速定位键值对的存储位置。

另外,“哈希”这个词来源于英文的“hash”,其本义是切碎并搅拌,是一种将食材混合在一起的方法。在计算机科学中,“哈希”这个词被用来描述将任意长度的数据转换为固定长度字符串的过程,这个过程与切碎并搅拌的过程类似,因此得名。而哈希表则是指利用哈希算法实现的一种数据结构,它利用哈希算法来快速访问存储在数组或其他位置的数据。因此,虽然哈希算法和哈希表的概念不同,但它们都与“哈希”这个词有关联。

四、哈希表处理哈希冲突的几种方法。

哈希冲突是指两个不同的键通过哈希函数得到了相同的索引值。解决哈希冲突主要有以下几种方法:

1. 链地址法(Separate Chaining)

哈希表(Hash Table)利用哈希函数将键(Key)映射到桶(Bucket)中,从而实现快速查找、插入和删除操作。

基本原理
  1. 哈希函数:哈希函数是哈希表的核心,它将键(可以是任何类型)映射到一个整数,这个整数表示桶的位置。一个好的哈希函数能够尽可能均匀地将键映射到各个桶,以减少冲突。
  2. :桶是存储键值对的数据结构。在哈希表中,每个桶可以包含多个键值对,这是因为可能会有多个键被哈希到同一个桶中,这是所谓的“冲突”。
  3. 解决冲突:当两个或更多的键被哈希到同一个桶时,就需要解决冲突。常见的解决冲突的方法有链地址法和开放地址法。
链地址法
  1. 定义:链地址法是将所有哈希到同一个桶的键值对存储在一个链表中。当查找、插入或删除一个键值对时,只需要在相应的桶的链表中进行操作即可。
  2. 查找:从目标桶的链表头开始遍历,如果找到了匹配的键,则返回对应的值;如果遍历完整个链表都没有找到,则返回空或者表示未找到。
  3. 插入:将新的键值对添加到目标桶的链表头。如果链表为空(即该桶之前没有键值对),则直接将新键值对添加到链表头;如果链表不为空,则将新键值对添加到链表头,并更新前一个键值对的下一个指向新添加的键值对。
  4. 删除:从目标桶的链表中删除指定的键值对。从链表头开始遍历,找到匹配的键后,将其从链表中移除。
例如:

假设我们有一个简单的哈希表,用于存储学生的姓名和年龄。哈希函数的目的是将姓名映射到一个整数(桶)。

  1. 初始化:创建一个空的哈希表。假设我们有5个桶,即0-4。
  2. 插入数据:将学生的姓名和年龄插入哈希表。例如,插入"张三"和"李四",他们的年龄分别是20和22。假设"张三"被哈希到桶0,"李四"被哈希到桶1。
  3. 查找数据:如果我们想查找"张三"的年龄,我们可以使用哈希函数找到桶0,然后从桶0的链表中查找"张三"。如果找到了,则返回对应的年龄;如果没有找到,则表示未找到。
  4. 删除数据:如果我们想删除"张三"的信息,我们可以在桶0的链表中找到"张三",并将其从链表中移除。
  5. 解决冲突:如果两个学生的姓名被哈希到了同一个桶,例如"王五"也被哈希到了桶0,那么我们就需要解决冲突。在这种情况下,我们可以将"王五"添加到桶0的链表中。
以下是一个简单的Python示例:
class HashTable:  def __init__(self):  self.size = 10  self.table = [None] * self.size  def hash_function(self, key):  return key % self.size  def insert(self, key, value):  hash_key = self.hash_function(key)  key_exists = self.table[hash_key]  if key_exists is None:  self.table[hash_key] = [[key, value]]  else:  for pair in key_exists:  if pair[0] == key:  pair[1] = value  # Update the value if key already exists  return  self.table[hash_key].append([key, value])  # Append the new pair if key doesn't exist  def get(self, key):  hash_key = self.hash_function(key)  key_exists = self.table[hash_key]  if key_exists is None:  return None  for pair in key_exists:  if pair[0] == key:  return pair[1]  return None

这个哈希表类包含以下方法:

  • __init__: 初始化一个大小为10的哈希表。这里定义了一个名为HashTable的类。当我们创建一个新的HashTable对象时,它首先初始化一个大小为10的数组(这可以看作是10个桶)。
  • hash_function: 计算键的哈希值。这是一个简单的哈希函数,它将一个键(可以是任何整数)映射到0到9的范围(因为我们有10个桶)。这里使用了模运算。
  • insert: 将键值对插入哈希表。首先,它计算键的哈希值来确定应该将键值对放在哪个桶中。然后,它检查该桶是否已包含一个键值对。如果该桶为空,则直接将新键值对放入该桶;如果该桶已包含一个键值对,则遍历该桶中的所有键值对,查找是否已存在具有相同键的键值对。如果找到了,则更新该键的值;如果没有找到,则将新键值对添加到该桶中。
  • get: 根据键从哈希表中检索值。它首先计算键的哈希值,然后查找该桶中的键值对。如果该桶为空,则返回None;否则,遍历该桶中的所有键值对,查找与给定键匹配的键值对。如果找到了,则返回该键的值;否则返回None。
另一个链地址法解决冲突的例子

使用Python中的列表代表桶和链表。通过每个桶(bucket)维护一个链表,所有散列到该桶的元素都会被加入这个链表中。

class HashTable:def __init__(self, size):self.size = sizeself.table = [[] for _ in range(self.size)]def hash_function(self, key):return key % self.sizedef insert(self, key, value):bucket_index = self.hash_function(key)bucket = self.table[bucket_index]for kv in bucket:if kv[0] == key:kv[1] = value  # Update existing keyreturnbucket.append([key, value])  # Insert new keydef search(self, key):bucket_index = self.hash_function(key)bucket = self.table[bucket_index]for kv in bucket:if kv[0] == key:return kv[1]return Nonedef remove(self, key):bucket_index = self.hash_function(key)bucket = self.table[bucket_index]for idx, kv in enumerate(bucket):if kv[0] == key:del bucket[idx]# 使用链地址法的哈希表
hash_table = HashTable(10)
hash_table.insert(1, 'value1')
hash_table.insert(11, 'value11')
print(hash_table.search(1))  # Output: value1
print(hash_table.search(11))  # Output: value11
hash_table.remove(1)
print(hash_table.search(1))  # Output: None

2. 开放地址法(Open Addressing)

当产生冲突时,开放地址法会查找哈希表中的下一个空槽,并将元素插入。以线性探测(Linear Probing)作为例子:

class HashTable:def __init__(self, size):self.size = sizeself.table = [None] * self.sizedef hash_function(self, key):return key % self.sizedef linear_probing(self, key, value):original_index = index = self.hash_function(key)while self.table[index] is not None:if self.table[index][0] == key:self.table[index][1] = value  # Update valuereturnindex = (index + 1) % self.sizeif index == original_index:  # The table is fullraise Exception('Hashtable is full')self.table[index] = [key, value]  # Insert new valuedef search(self, key):index = self.hash_function(key)while self.table[index] is not None:if self.table[index][0] == key:return self.table[index][1]index = (index + 1) % self.sizereturn Nonedef remove(self, key):index = self.hash_function(key)while self.table[index] is not None:if self.table[index][0] == key:self.table[index] = Nonereturn  # Key found and deletedindex = (index + 1) % self.size# 使用线性探测的哈希表
hash_table = HashTable(10)
hash_table.linear_probing(1, 'value1')
hash_table.linear_probing(11, 'value11')
print(hash_table.search(1))  # Output: value1
print(hash_table.search(11))  # Output: value11
hash_table.remove(1)
print(hash_table.search(1))  # Output: None

3. 双重散列(Double Hashing)

双重散列使用两个哈希函数来计算索引值,当第一个哈希函数发生冲突时,将会使用第二个哈希函数计算出新的位置。

以下是一个简单的Python程序,演示了如何使用双重散列(Double Hashing)处理哈希冲突:

class DoubleHashTable:  def __init__(self, size):  self.size = size  self.table = [None] * size  self.load = 0.0  def hash_function(self, key, i):  return (key % self.size + i) % self.size  def insert(self, key, value):  hash_key = self.hash_function(key, 1)  if self.table[hash_key] is None:  self.table[hash_key] = [[key, value]]  self.load = self.load + 1 / self.size  else:  for pair in self.table[hash_key]:  if pair[0] == key:  pair[1] = value  # Update the value if key already exists  return  self.table[hash_key].append([key, value])  # Append the new pair if key doesn't exist  def get(self, key):  hash_key = self.hash_function(key, 1)  if self.table[hash_key] is None:  return None  for pair in self.table[hash_key]:  if pair[0] == key:  return pair[1]  return None

这个程序定义了一个名为DoubleHashTable的类,它包含以下方法:

  • __init__:初始化哈希表,指定哈希表的大小。
  • hash_function:计算键的哈希值。这里使用了双重散列算法,其中第二个参数为i,表示第二个散列函数。
  • insert:向哈希表中插入一个键值对。首先计算键的哈希值,然后检查该位置是否已存在一个键值对。如果该位置为空,则将新键值对放入该位置;如果已存在键值对,则遍历该位置的键值对,查找与给定键匹配的键值对。如果找到了,则更新该键的值;如果没有找到,则将新键值对添加到该位置。同时,更新哈希表的负载因子。
  • get:根据键从哈希表中检索值。首先计算键的哈希值,然后检查该位置是否为空。如果为空,则返回None;否则,遍历该位置的键值对,查找与给定键匹配的键值对。如果找到了,则返回该键的值;如果没有找到,则返回None。

这个程序使用双重散列算法处理哈希冲突。当两个键的哈希值相同时,第二个散列函数会根据i的值进行计算,从而将它们映射到不同的位置。这样就可以避免哈希冲突的发生。

五、在编程语言的标准库中,如何使用哈希表

在实际应用中,比如在编程语言的标准库中,哈希表的实现往往是高度优化的,并结合以上多种技术来减少冲突的概率以及解决冲突。 

在多数现代编程语言的标准库中,都包含了某种形式的哈希表实现。这些通常被封装成字典、散列表或者映射等高级数据类型。下面分别以Python和Java为例,说明如何使用这些标准库中的哈希表。

Python

在Python中,内置的`dict`类型就是一个哈希表的实现。使用方式非常直接和简单。下面是一个简单的例子:

# 定义一个字典
my_dict = {'apple': 'a fruit', 'beetroot': 'a vegetable', 'cat': 'an animal'}# 访问字典
print(my_dict['apple'])   # 输出: a fruit# 更新字典
my_dict['dog'] = 'an animal'  # 添加新项
my_dict['apple'] = 'a tasty fruit'  # 更新已有项# 遍历字典
for key, value in my_dict.items():print(f'{key}: {value}')# 删除元素
del my_dict['beetroot']print(my_dict)

Java

在Java中,`HashMap`是一种基于哈希表的Map接口的实现。它是存储键值对的一个容器,每个键最多有一个值。以下是如何在Java中使用`HashMap`的例子:

import java.util.HashMap;public class TestHashMap {public static void main(String[] args) {// 创建HashMap对象HashMap<String, String> myMap = new HashMap<>();// 添加键值对myMap.put("apple", "a fruit");myMap.put("beetroot", "a vegetable");myMap.put("cat", "an animal");// 访问元素System.out.println(myMap.get("apple"));  // 输出: a fruit// 更新HashMapmyMap.put("dog", "an animal");  // 添加新项myMap.replace("apple", "a tasty fruit");  // 更新已有项// 遍历HashMapfor (String key : myMap.keySet()) {System.out.println(key + ": " + myMap.get(key));}// 删除元素myMap.remove("beetroot");System.out.println(myMap);}
}

在这两个示例中,我们看到的是如何创建哈希表、添加元素、访问元素、更新和删除元素等操作。在实际的应用中,还可能涉及到更多复杂的操作,如遍历键值对、查找是否包含某个键或值、清空哈希表、获取大小等。基本上,所有的现代编程语言都为哈希表提供了丰富的接口来满足日常编程的需求。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/589990.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

算法通关村第二十关-白银挑战图的存储与遍历

大家好我是苏麟, 今天继续聊图 . 与前面的链表、树等相比&#xff0c;图的存储和遍历要复杂非常多 .所以理解就好 , 面试基本不会让写代码的 . 图的类型多、表示方式多&#xff0c;相关算法也很多&#xff0c;实现又过于复杂&#xff0c;多语言实现难度太大了。这些算法一般理…

C语言之scanf浅析

前言&#xff1a; 当有了变量&#xff0c;我们需要给变量输入值就可以使用scanf函数&#xff0c;如果需要将变量的值输出在屏幕上的时候可以使用printf函数&#xff0c;如&#xff1a; #include <stdio.h> int main() {int score 0;printf("请输⼊成绩:");sc…

软件工程总复习笔记

软件工程课程复习提纲 文章目录 软件工程课程复习提纲一、基本知识点1. 软件工程的概念及目标2. 软件危机的概念及典型表现3. 瀑布模型的概念及特点4. 快速原型模型的特点5. 螺旋模型的基本思想6. 软件生命周期的概念及划分为哪几个阶段7. 软件需求的定义8. 常见的软件需求获取…

Java位运算及移位运算

java中能表示整数数据类型的有byte、short、char、int、long&#xff0c;在计算机中占用的空间使用字节描述&#xff0c;1个字节使用8位二进制表示。 数据类型字节数二进制位数表示范围默认值byte18-27 – 27-10char2160 – 216-1\u0000 (代表字符为空 转成int就是0)short216-…

OpenCV-12绘制图像

OpenCV提供了许多绘制图像的API&#xff0c;可以在图像上绘制各种图形&#xff0c;例如直线&#xff0c;矩形&#xff0c;圆&#xff0c;椭圆等图形。 一、画直线 利用API line&#xff08;img, pt1, pt2, color, thickness, lineType, shift&#xff09;可以绘制直线。 其中…

玩转贝启科技BQ3588C开源鸿蒙系统开发板 —— 代码下载(1)

本文主要参考&#xff1a; BQ3588C_代码下载 1. 安装依赖工具 安装命令如下&#xff1a; sudo apt-get update && sudo apt-get install binutils git git-lfs gnupg flexbison gperf build-essential zip curl zlib1g-dev gcc-multilib g-multiliblibc6-dev-i386 l…

【编译原理】期末预习做题向I

新的一年希望可以成为更好的人嘿嘿&#xff01; 这一篇基本就是把 up 讲的题都截了一遍然后加了点自己的笔记啥的 O.o &#xff08;不妥的话会删掉的 qwq&#xff0c;希望没事嘿嘿&#xff09; 来源&#xff1a;混子速成 I. 绪论 记住组成部分 II. 前后无关文法和语言 1.…

Python从入门到网络爬虫、自动化

可以创建C、C#、Python、Golang、Java、React、Node、Vue、PHP项目 创建Java项目 创建Python项目 简单if……else……语句 # 简单的if……else……语句 state True if state:print("状态正常") else:print("状态异常")# 复杂的if……elif……语句 score …

一骑绝尘!维乐携手骑行侠客轻风逆旅带你解锁冬日逆旅

是逆风冬旅还是冻旅&#xff1f;冬日似乎都被骑友们默认做事应该闭关闭的时间了&#xff0c;空气中萧瑟的寒风仿佛是穿透我们的骨膜&#xff0c;当我还在路上瑟瑟发抖的时候&#xff0c;此时一位公路骑行侠正在开启他的冬日旅途~      以下是来自他的自诉&#xff1a;   …

saas 多租户系统数据隔离方案

关注WX公众号&#xff1a; commindtech77&#xff0c; 获得数据资产相关白皮书下载地址 1. 回复关键字&#xff1a;数据资源入表白皮书 下载 《2023数据资源入表白皮书》 2. 回复关键字&#xff1a;光大银行 下载 光大银行-《商业银行数据资产会计核算研究报告》 3. 回复关键字…

【STM32F103】SysTick系统定时器延时函数

SysTick SysTick是Cortex-M3内核中的一个外设&#xff0c;内嵌在NVIC中&#xff0c;叫系统定时器。 当处理器在调试期间被喊停时&#xff0c;SysTick也将暂停运作。 一共有四个寄存器&#xff0c;不过我们通常用前三个&#xff0c;不需要校准。下图出自《STM32F10xxx Cortex…

关于SIC 的Know-how

SiC的分类和用途 根据电阻率不同&#xff0c;SiC衬底晶片可分为导电型和半绝缘型。 SiC衬底晶片主要用来做成高压功率器件和高频功率器件。其中&#xff0c;导电型SiC衬底晶片经过SiC外延后&#xff08;SiC基SiC外延片&#xff09;&#xff0c;主要应用于制造耐高温、耐高压的…

72内网安全-域横向CSMSF联动及应急响应知识

拿到才行&#xff0c;拿不到就是多余的 案例一MSF&CobaltStrike 联动 Shell 有一些功能可能cs或者msf强大一些&#xff0c;他们两个可以相互调用&#xff0c;在真实情况下也是可以cs和msf同时启动的&#xff0c; cs移交给msf .创建Foreign监听器 “Listeners”“Add”…

蓝桥杯一维差分 | 算法基础

⭐简单说两句⭐ ✨ 正在努力的小新~ &#x1f496; 超级爱分享&#xff0c;分享各种有趣干货&#xff01; &#x1f469;‍&#x1f4bb; 提供&#xff1a;模拟面试 | 简历诊断 | 独家简历模板 &#x1f308; 感谢关注&#xff0c;关注了你就是我的超级粉丝啦&#xff01; &…

Python-docx 深入word源码 带有序号的段落无法设置段后、段前距离、间距等段落属性

如果使用p doc.add_paragraph(内容, styleList Number)来创建序号段落&#xff0c;会发现设置序号段落之间的段前、段后以及段落间距无法生效。后来将docx库生成的word文档打开后发现段落的设置出现问题&#xff0c;如下图红框所示&#xff0c;将该选项去掉即可使段落间距属性…

【AIGC-图片生成视频系列-4】DreamTuner:单张图像足以进行主题驱动生成

目录 一. 项目概述 问题&#xff1a; 解决&#xff1a; 二. 方法详解 a) 整体结构 b) 自主题注意力 三. 文本控制的动漫角色驱动图像生成的结果 四. 文本控制的自然图像驱动图像生成的结果 五. 姿势控制角色驱动图像生成的结果 2023年的最后一天&#xff0c;发个文记录…

迈向通用异常检测和理解:大规模视觉语言模型(GPT-4V)率先推出

PAPERCODEhttps://arxiv.org/pdf/2311.02782.pdfhttps://github.com/caoyunkang/GPT4V-for-Generic-Anomaly-Detection 图1 GPT-4V在多模态多任务异常检测中的综合评估 在这项研究中&#xff0c;我们在多模态异常检测的背景下对GPT-4V进行了全面评估。我们考虑了四种模式&#…

BikeDNA(二) OSM数据的内在分析1

BikeDNA&#xff08;二&#xff09; OSM数据的内在分析1 该笔记本分析给定区域的 OSM 自行车基础设施数据的质量。 质量评估是“内在的”&#xff0c;即仅基于一个输入数据集&#xff0c;而不使用外部信息。 对于将 OSM 数据与用户提供的参考数据集进行比较的外在质量评估&…

mysql原理--optimizer trace表的神器功效

1.概述 设计 MySQL 的大叔提出了一个 optimizer trace 的功能&#xff0c;这个功能可以让我们方便的查看优化器生成执行计划的整个过程&#xff0c;这个功能的开启与关闭由系统变量 optimizer_trace 决定。 如果想打开这个功能&#xff0c;必须首先把 enabled 的值改为 on &am…

力扣回溯算法-电话号码的字母组合

力扣第17题&#xff0c;电话号码的字母组合 题目 给定一个仅包含数字 2-9 的字符串&#xff0c;返回所有它能表示的字母组合。 给出数字到字母的映射如下&#xff08;与电话按键相同&#xff09;。注意 1 不对应任何字母。 .电话号码的字母组合 示例: 输入&#xff1a;“2…