算法:字符串哈希

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

基本概念

哈希函数

hash(s)=∑i=0n−1si×pn−i−1(modM) hash(s) = \sum_{i=0}^{n-1} si \times p^{n-i-1} \pmod{M} hash(s)=i=0∑n−1si×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算法的基础

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

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

相关推荐
CoderYanger18 分钟前
A.每日一题:1140. 石子游戏 II
java·程序人生·算法·leetcode·游戏·职场和发展·深度优先
小雨笙笙21 分钟前
机器学习:评估模型与选择
人工智能·算法·机器学习
高亦真25 分钟前
今天是学习嵌入式的第35天
linux·学习·算法
mengzhi啊28 分钟前
QEventLoop loop配合loop.exec();替代while(1)。电脑cpu占用为0,使用while(1)cpu占用率100%
c++·qt
HugoStudio_SWAN37 分钟前
洛谷 P1319 / P1320 压缩技术——同一枚硬币的正反面
c++·学习·程序人生·算法
小小晓.1 小时前
C++单例模式万字详解:6种实现演进+线程安全彻底解决+CRTP终极模板
c++·单例模式
疯狂打码的少年1 小时前
【数据库技术】SQL嵌套查询(IN/EXISTS/ANY/ALL)
数据库·笔记·sql
凯尔萨厮2 小时前
Java学习笔记十四(网络通信)
笔记·学习
AAA代码批发商2 小时前
DAYS 38 TCP并发服务器模型详解
linux·网络·笔记·学习
j7~2 小时前
【C++11】C++11 新特性全套详解(列表初始化、右值引用、lambda 表达式、function 与 bind 包装器等)
c++·右值引用·lambda表达式·可变参数模板·移动语义·包装器·c++11发展史