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

一、哈希算法

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

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

  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 = size
        self.table = [[] for _ in range(self.size)]

    def hash_function(self, key):
        return key % self.size

    def 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 key
                return
        bucket.append([key, value])  # Insert new key
    
    def 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 None

    def 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 = size
        self.table = [None] * self.size

    def hash_function(self, key):
        return key % self.size

    def 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 value
                return

            index = (index + 1) % self.size
            if index == original_index:  # The table is full
                raise Exception('Hashtable is full')

        self.table[index] = [key, value]  # Insert new value

    def 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.size

        return None

    def remove(self, key):
        index = self.hash_function(key)
        while self.table[index] is not None:
            if self.table[index][0] == key:
                self.table[index] = None
                return  # Key found and deleted
            index = (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

        // 更新HashMap
        myMap.put("dog", "an animal");  // 添加新项
        myMap.replace("apple", "a tasty fruit");  // 更新已有项

        // 遍历HashMap
        for (String key : myMap.keySet()) {
            System.out.println(key + ": " + myMap.get(key));
        }

        // 删除元素
        myMap.remove("beetroot");

        System.out.println(myMap);
    }
}

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

docker里面不能使用vim的解决办法

docker里面不能使用vim的解决办法 目录 docker里面不能使用vim的解决办法 1.在使用时会出现 2.在使用这些都不能解决的时候考虑 3.测试是否可用 1.在使用时会出现 bash: vim: command not found 出现这种错误时首先考虑使用 apt-get update 然后在用 apt-get install …

2024 Win 安装Oracle12C

文章目录 一、下载1.1 官方下载1.2 官方Archive下载1.3 博主提供 二、安装2.1 解压2.2 安装 三、连接3.1 SQL Plus3.2 切换到容器数据库orclpdb3.3 查询SID 四、查看数据4.1 SQL Develop 连接4.2 创建新用户4.3 develop 直接创建新用户4.3.2 SQL 错误: ORA-65096: 公用用户名或…

App.vue中引入自定义组件

components目录中定义组件&#xff1a;Person.vue 目录截图&#xff1a; Person.vue文件中内容&#xff1a; <template><div class"person"><h2>姓名&#xff1a;{{name}}</h2><h2>年龄&#xff1a;{{age}}</h2><!--定义了…

LeetCode每日一题.04(不同路径)

一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff09;。 问总共有多少条不同的路径&#xff1f; 示例 1…

创建型设计模式 - 抽象工厂模式 - JAVA

创建型设计模式 - 抽象工厂设计模式 一. 简介二. 列子2.1 定义电脑的抽象类和子类2.2 定义抽象工厂类和其实现类2.3 测试 三. 抽象工厂设计模式的好处四. 抽象工厂模式的案例 前言 这是我在这个网站整理的笔记,有错误的地方请指出&#xff0c;关注我&#xff0c;接下来还会持续…

Linux 安装 mysql【使用yum源进行安装】

配置yum 源 首先&#xff0c;去到mysql网站&#xff0c;找到它的rpm的资源包 “mysql80-community-release-el9-5.noarch.rpm” 我们将其下载下来&#xff0c;然后配置yum源&#xff08;下面两种方式二选一即可&#xff09; ① 使用xftp传输&#xff0c;然后配置yum源 rpm …

从0到1入门C++编程——01 C++基础知识

文章目录 一、工具安装二、新建项目三、设置字体、注释、行号四、C基础知识1.数据类型2.输入输出3.运算符4.选择、循环结构5.跳转语句6.数组7.函数8.指针9.结构体 一、工具安装 学习C使用到的工具是Visual Studio&#xff0c;Visual Studio 2010旗舰版下载链接&#xff1a;点此…

Qt基础之四十五:Qt国际化(I18N)

国际化的英文表述为Internationalization,通常简写为I18N(首尾字母加中间的字符数),这种奇葩的缩写方式,让我想起了NBA球星“字母哥”。 下面看下Qt实现的动态语言切换效果。 一.效果 二.源码 QHSettingDialog.h #ifndef QHSETTINGDIALOG_H #define QHSETTINGDIALOG_H#…

虚拟专线网络(IP-VPN)

虚拟专线网络(IP-VPN)&#xff0c;因为它的安全性和可靠性。通过亚洲领先的 IP VPN 提供商。享受更高的可管理性和可扩展性&#xff0c;在多个站点之间交付 IP 流量或数据包&#xff0c;拥有亚太地区最大的 IP 骨干网。 1&#xff0c;保证正常运行时间&#xff0c;在网络链路发…

修改一个VC++访问数据库源码

下载一个VC6访问数据库的源码;修改; 打开工程先出现下图错误; 根据资料,出现此错误,解决方法: 1.如果用户不需要在 WizardBar,请关闭该的 WizardBar 并重新启动 Visual C++6.0。 如果但是,您想访问 WizardBar 功能,请关闭受影响的工作区之前关闭所有窗口。 2.重新生…

设计模式:抽象工厂模式(讲故事易懂)

抽象工厂模式 定义&#xff1a;将有关联关系的系列产品放到一个工厂里&#xff0c;通过该工厂生产一系列产品。 设计模式有三大分类&#xff1a;创建型模式、结构型模式、行为型模式 抽象工厂模式属于创建型模式 上篇 工厂方法模式 提到工厂方法模式中每个工厂只生产一种特定…

Docker九 | Swarm mode

目录 Swarm基本概念 节点 服务和任务 创建Swarm集群 创建管理节点 增加工作节点 查看集群 部署服务 新建服务 查看服务 服务伸缩 增加服务 减少服务 删除服务 Swarm基本概念 节点 节点分为管理节点(manager)和工作节点(worker) 管理节点 管理节点用于Swarm集群的…

【JavaFX】JDK11 基于Gson、hutool、Jackson持久化存储实体类数据的解决方案 (读取、追加、去重json对象)

文章目录 开发环境效果前言一、Gson是什么?二、使用步骤1.引入依赖2.创建实体类创建 JsonFileService类创建JsonFileService的实现类 JsonFileServiceImpl三、实现效果开发环境 JDK11IDEA 2023.3Gson、hutool、JacksonJavaFX 11效果 前言 使用JDK1

Langchain-Chatchat开源库使用的随笔记(一)

笔者最近在研究Langchain-Chatchat&#xff0c;所以本篇作为随笔记进行记录。 最近核心探索的是知识库的使用&#xff0c;其中关于文档如何进行分块的详细&#xff0c;可以参考笔者的另几篇文章&#xff1a; 大模型RAG 场景、数据、应用难点与解决&#xff08;四&#xff09;R…

Spring Cloud + Vue前后端分离-第10章 基于阿里云OSS的文件上传

源代码在GitHub - 629y/course: Spring Cloud Vue前后端分离-在线课程 Spring Cloud Vue前后端分离-第10章 基于阿里云OSS的文件上传 前面介绍的文件上传是基于本地文件服务器的文件上传&#xff0c;但是自己搭文件服务器会有很多运维的问题&#xff0c;比如磁盘满了要扩容…

VMware安装RHEL9.0版本Linux系统

最近在学习Linux&#xff0c;安装了Red Hat Enterprise Linux 的 9.0版本&#xff0c;简称RHEL9.0。RHEL9.0是Red Hat公司发布的面向企业用户的Linux操作系统的最新版本。我把它安装在虚拟机VMware里来减少电脑性能占用&#xff0c;也防止系统炸搞得我后面要重装。安装RHEL9.0还…

【Unity入门】MenuItem 和 ContextMenu 的使用方法

目录 一、ContextMenu描述使用示例ContextMenuItem使用示例 二、MenuItem描述使用示例 三、MenuItem 和 ContextMenu 的区别 一、ContextMenu 描述 ContextMenu 属性用于向上下文菜单添加命令。 在该附加脚本的 Inspector 中&#xff0c;当用户选择该上下文菜单时&#xff0c…

FA组件详解

1、了解FA核心组件以及功能 &#xff08;1&#xff09;TC&#xff08;Thin Client&#xff1a;瘦终端&#xff09;&#xff1a;就是类似于机顶盒的一个小盒子&#xff0c;里面有CPU、内存、USB、MIC、HDMI等接口&#xff0c;可以理解为小型电脑&#xff0c;但是它里面是没有操作…

Unity 新版 Meta XR SDK 无法导入解决方法

文章目录 &#x1f4d5;教程说明&#x1f4d5;新版 SDK 说明&#x1f4d5;从 Meta 官网导入开发包⭐依赖包⭐如何导入⭐导入后包存放在哪里了&#xff1f;⭐场景样例文件去哪了&#xff1f; 此教程相关的详细教案&#xff0c;文档&#xff0c;思维导图和工程文件会放入 Spatia…

Django 学习教程-介绍与安装

系列 Django 学习教程-第一个 Django 应用-CSDN博客 介绍 Django 是一个高级 Python Web 框架&#xff0c;它鼓励快速开发和干净、实用的设计。 它由经验丰富的开发人员构建&#xff0c;解决了 Web 开发的大部分麻烦&#xff0c;因此您可以专注于在编写应用程序时无需重新发…