1. 哈希表概述与核心概念
哈希表(Hash Table),又称散列表,是一种基于快速存取需求设计的数据结构。它通过哈希函数将关键字Key映射到表中特定位置来实现高效的数据存储与检索。在理想情况下,哈希表的查找、插入和删除操作都能达到O(1)的时间复杂度。
1.1 哈希表的基本工作原理
哈希表的核心机制包含三个关键部分:
- 哈希函数:负责将任意大小的数据映射到固定大小的值域(通常是数组索引)
- 冲突解决策略:处理不同键映射到同一位置的情况
- 扩容机制:当数据量增加时动态调整表的大小
哈希表的性能很大程度上取决于哈希函数的质量和冲突处理策略的效率。一个好的哈希函数应该满足以下特性:
- 计算速度快
- 结果分布均匀
- 对相似输入产生差异明显的输出
1.2 哈希冲突的本质
哈希冲突是指两个不同的键通过哈希函数计算得到了相同的索引位置。这是不可避免的现象,因为哈希函数通常会将一个大范围的键映射到一个小范围的索引中。冲突处理策略的好坏直接影响哈希表的实际性能。
在实际工程中,我们无法完全避免冲突,但可以通过以下方式减少冲突概率:
- 选择高质量的哈希函数
- 保持合理的负载因子
- 选择合适的表大小(特别是使用素数作为模数)
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 哈希函数详解与实现
2.1 直接定址法
直接定址法是最简单的哈希函数形式,适用于键值范围较小且连续的场景。其基本形式为:
cpp复制h(key) = key
或者带有偏移量的版本:
cpp复制h(key) = key - offset
2.1.1 应用实例:字符串中的第一个唯一字符
考虑LeetCode问题"387. First Unique Character in a String",我们可以使用直接定址法高效解决:
cpp复制int firstUniqChar(string s) {
int count[26] = {0};
// 统计每个字符出现次数
for(char c : s) {
count[c-'a']++;
}
// 找出第一个出现次数为1的字符
for(int i = 0; i < s.size(); ++i) {
if(count[s[i]-'a'] == 1) {
return i;
}
}
return -1;
}
这种方法的时间复杂度为O(n),空间复杂度为O(1)(因为字母表大小固定)。
2.2 除留余数法
除留余数法是最常用的哈希函数之一,形式为:
cpp复制h(key) = key % M
其中M最好是素数,这样可以减少冲突概率。选择素数的原因在于:
- 素数不会被其因数以外的数整除
- 减少了键值分布不均匀导致的聚集现象
- 对于随机输入,素数模数能提供更好的分布特性
2.2.1 素数选择策略
在实际实现中,我们可以预先准备一个素数表,按照大约两倍的增长率递增:
cpp复制static const int PRIMES[] = {
53, 97, 193, 389, 769, 1543, 3079, 6151,
12289, 24593, 49157, 98317, 196613, 393241,
786433, 1572869, 3145739, 6291469, 12582917,
25165843, 50331653, 100663319, 201326611,
402653189, 805306457, 1610612741
};
然后提供一个函数来获取合适的素数:
cpp复制size_t GetNextPrime(size_t num) {
const int* begin = PRIMES;
const int* end = PRIMES + sizeof(PRIMES)/sizeof(PRIMES[0]);
const int* pos = lower_bound(begin, end, num);
return (pos == end) ? *(end-1) : *pos;
}
2.3 字符串哈希函数
对于字符串类型的键,我们需要特殊的哈希函数。简单的ASCII码相加方法存在严重缺陷:
cpp复制// 不好的实现:容易产生冲突
size_t badHash(const string& s) {
size_t hash = 0;
for(char c : s) {
hash += c;
}
return hash;
}
2.3.1 BKDR哈希算法
BKDR哈希是一种效果良好的字符串哈希算法,其核心思想是使用一个素数作为乘数:
cpp复制size_t BKDRHash(const string& s) {
size_t hash = 0;
const size_t seed = 131; // 31, 131, 1313, 13131, etc.
for(char c : s) {
hash = hash * seed + c;
}
return hash;
}
这种算法的优势在于:
- 乘法操作有助于分散相似字符串的哈希值
- 使
