一文学懂哈希及如何手撕哈希算法(附详细例题)

一文学懂哈希及如何手撕哈希算法(附详细例题)

一、概念理解

哈希(Hashing)是计算机科学中一个非常重要的概念,广泛应用于数据存储、数据检索、加密、校验等场景。哈希的核心思想是将输入的数据(通常是任意长度)通过哈希函数映射为固定长度的输出,这个输出值称为哈希值或哈希码。

1. 哈希的基本概念

哈希是将任意长度的数据(例如文本、数字、文件等)通过某种算法转换成一个固定长度的值。哈希值通常是一个数字或字符序列,其长度固定。

哈希函数:哈希函数是将输入数据(称为“消息”或“键”)映射到一个固定大小的输出(哈希值)的数学函数。

对于任何输入值 xx,哈希函数 H(x)H(x) 会输出一个固定长度的值。常见的哈希函数包括 MD5、SHA-1、SHA-256 等。

哈希值(哈希码):是哈希函数的输出。它通常被用来在查找、验证、加密等过程中进行快速比较。

2. 哈希函数的性质

一个好的哈希函数需要满足以下几个基本性质:

确定性:对于相同的输入,哈希函数的输出应该是相同的。

快速计算:哈希函数应该能够快速地计算出哈希值,无论输入数据的大小如何。

均匀分布:理想情况下,哈希函数应该尽量将不同的输入数据映射到哈希值的不同位置,从而避免哈希冲突。也就是说,哈希值的分布应该尽量均匀。

抗碰撞性(Collision Resistance):不同的输入数据应该尽量产生不同的哈希值。如果不同输入数据产生相同的哈希值,称为哈希冲突(Collision)。好的哈希函数应该使得碰撞的概率非常低。

不可逆性:哈希函数应当是单向的,即从哈希值无法反推出原始数据。这样的性质是加密学中不可或缺的一部分。

抗预映像攻击:给定一个哈希值,应该很难找到一个输入数据,使得哈希值为该哈希值。

3. 常见的哈希算法

哈希算法在不同的领域有不同的应用。常见的哈希算法包括:

MD5(Message Digest Algorithm 5):

输出:128位(16字节)的哈希值,通常以32个字符的十六进制表示。应用:早期广泛用于文件校验、密码存储等,但由于其存在较多碰撞问题,现在已不推荐用于安全敏感的应用。

SHA-1(Secure Hash Algorithm 1):

输出:160位(20字节)的哈希值。应用:曾广泛用于数字签名、证书验证等,但现已被认为不再足够安全,已被逐渐淘汰。

SHA-256:

输出:256位(32字节)的哈希值。应用:SHA-2系列(包括SHA-256)目前被认为是相对安全的哈希算法,广泛应用于加密货币(如比特币)、数字签名、文件校验等领域。

CRC32:

输出:32位的哈希值,通常用于检查数据的完整性。应用:用于文件传输、存储系统中,检测数据是否被篡改。

BLAKE2:

输出:可定制长度的哈希值,性能优秀,安全性较高。应用:比SHA-2更加高效,广泛用于密码学、数字签名等。4. 哈希的应用

哈希有广泛的应用,以下是一些主要的应用场景:

4.1 数据结构:哈希表

哈希表(Hash Table)是一种基于哈希算法的常用数据结构。它通过哈希函数将键映射到数组的索引位置,从而提供快速的查找、插入和删除操作。哈希表广泛应用于数据库索引、缓存系统等。

哈希冲突(Collision):当不同的输入数据经过哈希函数映射到相同的位置时,就发生了哈希冲突。常用的解决哈希冲突的方法包括:

链式法(Chaining):每个哈希桶使用链表存储多个元素。若哈希值相同,则将元素插入链表中。开放寻址法(Open Addressing):当发生哈希冲突时,寻找数组中的下一个空位来存储元素,直到找到为止。4.2 数据完整性校验

哈希常用于验证数据的完整性。例如,在文件传输过程中,源和目标可以使用相同的哈希函数生成文件的哈希值,然后比较两个哈希值,若相同,则说明文件未被篡改。

校验和(Checksum):数据传输协议中,常常会用哈希值来校验数据的完整性。比如,FTP、HTTP传输文件时,通常会附带文件的哈希值(如MD5或SHA-1)。4.3 加密与数字签名

哈希算法在加密学中用于生成消息摘要,并结合公钥或私钥进行数字签名。数字签名可以验证消息的完整性和发送者身份。

数字签名:发送方用私钥对消息的哈希值进行加密,接收方通过公钥解密并与接收到的消息计算哈希值比对,从而验证消息是否被篡改。4.4 密码存储

在密码存储中,直接存储用户密码是不安全的,因此通常存储的是密码的哈希值。这样,即便数据库被攻击者获取,密码本身仍然保持安全。

盐(Salt):为了防止通过预计算哈希值表(如彩虹表)进行攻击,通常会在密码的哈希计算中加入一个随机生成的值(盐)。每个用户的盐值不同,从而保证即使两个用户的密码相同,哈希值也不同。4.5 区块链

在区块链中,哈希函数被用于确保数据不可篡改。每个区块的哈希值包含前一个区块的哈希值,形成一个链条,任何篡改都会导致整个链的哈希值变化,从而被网络中的节点察觉。

4.6 数据去重与唯一性检查

哈希函数可以用于去重和唯一性检查。例如,在大规模数据处理、数据库去重等场景中,可以通过计算数据的哈希值来判断数据是否重复。

5. 常见的哈希相关问题

哈希冲突:尽管哈希函数可以将不同的输入映射到固定大小的输出,但由于输出的空间有限,不同的输入可能会生成相同的哈希值(碰撞)。这对于哈希表、加密等应用来说是一个严重问题。解决哈希冲突的方法包括使用更复杂的哈希算法、增加哈希表的大小或采用链式存储等。

哈希碰撞攻击:攻击者可能通过巧妙的输入设计,使得两个不同的输入数据产生相同的哈希值,从而进行欺骗或篡改。这在使用不安全的哈希函数(如MD5、SHA-1)时较为容易发生。

6. 总结

哈希是一种非常重要的技术,广泛应用于计算机科学中的数据存储、加密、安全验证等领域。一个好的哈希函数需要具备快速计算、抗碰撞、不可逆等特性,而哈希算法的选择则依赖于具体的应用场景。在实际应用中,了解哈希的工作原理,掌握常见哈希算法的特性,能够帮助你在多个技术领域中更好地使用哈希。

二、手撕算法题

当然!以下是用 Java 编写的相应解答,展示了哈希在各种常见算法问题中的应用。

1. 查找和匹配

两数之和

给定一个整数数组和一个目标值,找出数组中两个数之和等于目标值的索引。我们可以使用哈希表来加速查找过程,避免使用暴力算法。

import java.util.HashMap;

public class TwoSum {

public static int[] twoSum(int[] nums, int target) {

HashMap map = new HashMap<>();

for (int i = 0; i < nums.length; i++) {

int complement = target - nums[i];

if (map.containsKey(complement)) {

return new int[] { map.get(complement), i };

}

map.put(nums[i], i);

}

throw new IllegalArgumentException("No solution");

}

public static void main(String[] args) {

int[] nums = {2, 7, 11, 15};

int target = 9;

int[] result = twoSum(nums, target);

System.out.println("Indices: " + result[0] + ", " + result[1]);

}

}

2. 去重

去重数组中的元素

使用哈希集合(HashSet)来去除重复的元素,可以在 O(n) 时间内完成。

import java.util.HashSet;

public class RemoveDuplicates {

public static int removeDuplicates(int[] nums) {

HashSet set = new HashSet<>();

int index = 0;

for (int num : nums) {

if (set.add(num)) {

nums[index++] = num;

}

}

return index;

}

public static void main(String[] args) {

int[] nums = {1, 1, 2, 2, 3, 4};

int length = removeDuplicates(nums);

for (int i = 0; i < length; i++) {

System.out.print(nums[i] + " ");

}

}

}

3. 计数和频率统计

最常见的元素

我们可以使用 HashMap 来记录每个元素的频率,最后找出出现最多的元素。

import java.util.HashMap;

import java.util.Map;

public class MajorityElement {

public static int majorityElement(int[] nums) {

HashMap countMap = new HashMap<>();

for (int num : nums) {

countMap.put(num, countMap.getOrDefault(num, 0) + 1);

}

int majorityElement = nums[0];

int majorityCount = 0;

for (Map.Entry entry : countMap.entrySet()) {

if (entry.getValue() > majorityCount) {

majorityElement = entry.getKey();

majorityCount = entry.getValue();

}

}

return majorityElement;

}

public static void main(String[] args) {

int[] nums = {3, 2, 3};

System.out.println("Majority Element: " + majorityElement(nums));

}

}

4. 字符串和子串问题

无重复字符的最长子串

使用滑动窗口技术,并用哈希表记录窗口内的字符,更新子串的最大长度。

import java.util.HashMap;

public class LongestSubstring {

public static int lengthOfLongestSubstring(String s) {

HashMap map = new HashMap<>();

int left = 0, maxLength = 0;

for (int right = 0; right < s.length(); right++) {

if (map.containsKey(s.charAt(right))) {

left = Math.max(left, map.get(s.charAt(right)) + 1);

}

map.put(s.charAt(right), right);

maxLength = Math.max(maxLength, right - left + 1);

}

return maxLength;

}

public static void main(String[] args) {

String s = "abcabcbb";

System.out.println("Longest Substring Length: " + lengthOfLongestSubstring(s));

}

}

5. 动态规划和哈希表的结合

爬楼梯问题

用动态规划和哈希表来优化存储并解决问题。

import java.util.HashMap;

public class ClimbStairs {

public static int climbStairs(int n) {

HashMap dp = new HashMap<>();

dp.put(0, 1); // 1 way to stay at the ground

dp.put(1, 1); // 1 way to step 1 stair

for (int i = 2; i <= n; i++) {

dp.put(i, dp.get(i - 1) + dp.get(i - 2));

}

return dp.get(n);

}

public static void main(String[] args) {

int n = 5;

System.out.println("Ways to climb " + n + " stairs: " + climbStairs(n));

}

}

6. 字谜问题

字谜判断

使用哈希表来统计字符频率,判断两个字符串是否为字谜。

import java.util.HashMap;

public class CheckInclusion {

public static boolean checkInclusion(String s1, String s2) {

if (s1.length() > s2.length()) return false;

HashMap countMap = new HashMap<>();

for (char c : s1.toCharArray()) {

countMap.put(c, countMap.getOrDefault(c, 0) + 1);

}

int left = 0, right = 0, count = countMap.size();

while (right < s2.length()) {

char rightChar = s2.charAt(right);

if (countMap.containsKey(rightChar)) {

countMap.put(rightChar, countMap.get(rightChar) - 1);

if (countMap.get(rightChar) == 0) count--;

}

while (count == 0) {

if (right - left + 1 == s1.length()) return true;

char leftChar = s2.charAt(left);

if (countMap.containsKey(leftChar)) {

countMap.put(leftChar, countMap.get(leftChar) + 1);

if (countMap.get(leftChar) > 0) count++;

}

left++;

}

right++;

}

return false;

}

public static void main(String[] args) {

String s1 = "ab";

String s2 = "eidbaooo";

System.out.println("Is Inclusion: " + checkInclusion(s1, s2));

}

}

总结

在 Java 中,哈希表(如 HashMap 和 HashSet)提供了高效的查找、去重、计数等操作。在解决算法题时,合理利用哈希能够显著提高效率,尤其是当你面对需要频繁查找或存储某些数据时。

对于查找问题,可以利用哈希表进行快速查找。对于去重问题,使用哈希集合可以在 O(n) 时间内去除重复元素。对于频率统计,使用哈希表统计元素的出现次数并可以快速找出最多的元素。对于子串问题,哈希表常与滑动窗口技术结合使用,能够有效地优化字符串处理。

相关推荐

买了一个月的iPhone换新的要加多少钱?
日博官网365bet

买了一个月的iPhone换新的要加多少钱?

07-09 阅读 3047
DNF兽人守卫战全攻略
日博官网365bet

DNF兽人守卫战全攻略

08-13 阅读 1312
天猫预售是什么意思,又该如何设置呢?
365bet中国客服电话

天猫预售是什么意思,又该如何设置呢?

08-12 阅读 6203