字符串哈希

831 字
4 分钟
字符串哈希

基本概念#

哈希函数#

hash(s)=∑i=0n−1s[i]×pn−i−1(modM)hash(s) = \sum_{i=0}^{n-1} s[i] \times p^{n-i-1} \pmod{M}
  • 定义:将关键字映射成对应地址的函数,记作 Hash(key)=Addr
  • 特性:哈希函数可能会把两个或两个以上的不同关键字映射到统一地址(哈希冲突)

字符串哈希#

  • 核心思想:将一个字符串用一个整数表示
  • 应用场景:字符串匹配,快速比较子串,字符串搜索等

哈希函数设计#

常用参数#

// p 通常取质数 131 或 13331
const int P = 13331; // 或 131
// 利用 unsigned long long 的自然溢出实现自动取模
typedef unsigned long long ULL;

哈希计算原理#

对于字符串“abc”,哈希值计算为:

hash = a × p² + b × p¹ + c × p⁰

前缀哈希法#

数据结构定义#

typedef unsigned long long ULL;
const int N = 1e6 + 10, P = 13331;
char s[N]; // 字符串数组,下标从1开始
int len; // 字符串长度
ULL f[N]; // 前缀哈希数组
ULL p[N]; // p的i次方数组

初始化处理#

void init_hash() {
f[0] = 0;
p[0] = 1;
for (int i = 1; i <= len; i++) {
f[i] = f[i - 1] * P + s[i]; // 计算前缀哈希
p[i] = p[i - 1] * P; // 计算p的幂次
}
}

获取任意子串哈希值#

ULL get_hash(int l, int r) {
return f[r] - f[l - 1] * p[r - l + 1];
}
//假设字符串“abc”,需要子串“bc”哈希,则要“abc”-"a";注意“a”为高位*过2次(下标为2)
//故f[r] - f[l - 1] * p[r - l + 1]-->"abc"-"a"*(a下标)

单个字符串哈希计算#

ULL get_simple_hash() {
ULL ret = 0;
for (int i = 1; i <= len; i++) {
ret = ret * P + s[i];
}
return ret;
}
//无需计算p[i];

完整代码示例#

#include <iostream>
#include <cstring>
using namespace std;
typedef unsigned long long ULL;
const int N = 1e6 + 10, P = 13331;
class StringHash {
private:
char s[N];
int len;
ULL f[N], p[N];
public:
void init(const char* str) {
len = strlen(str);
// 字符串下标从1开始
for (int i = 1; i <= len; i++) {
s[i] = str[i - 1];
}
init_hash();
}
void init_hash() {
f[0] = 0;
p[0] = 1;
for (int i = 1; i <= len; i++) {
f[i] = f[i - 1] * P + s[i];
p[i] = p[i - 1] * P;
}
}
ULL get_hash(int l, int r) {
if (l < 1 || r > len || l > r) return 0;
return f[r] - f[l - 1] * p[r - l + 1];
}
ULL get_simple_hash() {
ULL ret = 0;
for (int i = 1; i <= len; i++) {
ret = ret * P + s[i];
}
return ret;
}
};
int main() {
StringHash sh;
sh.init("hello world");
// 获取子串 "hello" 的哈希值
ULL hash1 = sh.get_hash(1, 5);
cout << "Hash of 'hello': " << hash1 << endl;
// 获取子串 "world" 的哈希值
ULL hash2 = sh.get_hash(7, 11);
cout << "Hash of 'world': " << hash2 << endl;
return 0;
}

注意事项#

  1. 字符串下标:通常从1开始,避免边界处理问题
  2. 参数选择:P取质数131或13331,减少哈希冲突
  3. 溢出利用:ULL类型溢出自动取模2^64
  4. 哈希冲突:虽然概率很低,但在关键应用中可能需要双哈希
  5. 初始化顺序:必须先初始化p数组,再计算前缀哈希

应用场景#

· 快速比较子串:O(1)时间比较任意两个子串是否相等 · 字符串匹配:Rabin-Karp算法的基础 · 回文判断:结合正向和反向哈希 · 最长公共子串:二分答案+哈希检查(二分)

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

Profile Image of the Author
HandlerOne
おかえり
公告
本站已整理 138 篇技术笔记,并按 8 条主线排成学习路线(141 站);站内搜索与桌宠对话均已上线。
分类
标签
最新动态
站点统计
文章
138
分类
12
标签
42
总字数
597,044
站点信息
构建平台
腾讯云 · Nginx
博客版本
v6.16.8
文章目录