算法:字符串哈希

#预处理 #前缀 #基础算法

基本概念

哈希函数

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(s)=i=0∑n−1s[i]×pn−i−1(modM)

  • 定义:将关键字映射成对应地址的函数,记作 Hash(key)=Addr
  • 特性:哈希函数可能会把两个或两个以上的不同关键字映射到统一地址(哈希冲突)

字符串哈希

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

哈希函数设计

常用参数

cpp 复制代码
// p 通常取质数 131 或 13331
const int P = 13331;  // 或 131

// 利用 unsigned long long 的自然溢出实现自动取模
typedef unsigned long long ULL;

哈希计算原理

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

cpp 复制代码
hash = a × p² + b × p¹ + c × p⁰

前缀哈希法

数据结构定义

cpp 复制代码
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次方数组

初始化处理

cpp 复制代码
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的幂次
    }
}

获取任意子串哈希值

cpp 复制代码
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下标)

单个字符串哈希计算

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

完整代码示例

cpp 复制代码
#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算法的基础

· 回文判断:结合正向和反向哈希

· 最长公共子串:二分答案+哈希检查([[二分算法]])

相关推荐
&&Citrus1 天前
【CPN学习笔记(二)】Chap2 非分层颜色 Petri 网——从一个简单协议开始读懂 CPN
笔记·学习·php·cpn·petri网
汀、人工智能1 天前
[特殊字符] 第40课:二叉树最大深度
数据结构·算法·数据库架构·图论·bfs·二叉树最大深度
沉鱼.441 天前
第十二届题目
java·前端·算法
HXQ_晴天1 天前
Linux 磁盘清理 & 查看常用指令笔记
笔记
赫瑞1 天前
数据结构中的排列组合 —— Java实现
java·开发语言·数据结构
大熊背1 天前
ISP Pipeline中Lv实现方式探究之三--lv计算定点实现
数据结构·算法·自动曝光·lv·isppipeline
初夏睡觉1 天前
c++1.3(变量与常量,简单数学运算详解),草稿公放
开发语言·c++
升职佳兴1 天前
C盘爆满自救:3步无损迁移应用数据到E盘(含回滚)
c语言·开发语言
西岸行者1 天前
BF信号是如何多路合一的
算法
阿拉斯攀登1 天前
从入门到实战:CMake 与 Android JNI/NDK 开发全解析
android·linux·c++·yolo·cmake