算法:字符串哈希

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

基本概念

哈希函数

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算法的基础

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

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

相关推荐
青山木3 分钟前
Hot 100 --- 最小栈
java·数据结构·算法·leetcode
疯狂打码的少年6 分钟前
【数据结构】栈的应用:表达式求值(后缀表达式)
java·数据结构·笔记·算法
HiDev_17 分钟前
【非标自动化】plc执行顺序问题、扫描周期问题
深度学习·算法·机器学习
高洁011 小时前
工信部教考中心证书-人工智能系列本系列
人工智能·python·深度学习·算法
10岁的博客1 小时前
【C++题解】最多共线点问题:小飞拿蛋糕游戏的最优策略
c++
陈王卜1 小时前
Reduce 算子优化笔记
算法
wabs6661 小时前
关于图论【最短路径之Bellman_ford 算法(判断负权回路)|卡码网95.城市间货物运输II的思考】
数据结构·算法·图论·bellman_ford算法·卡码网·判断负权回路
東隅已逝,桑榆非晚1 小时前
类和对象(中)
c++·笔记
YouonlyliveonceC1 小时前
《大话数据结构》第4章实战:中缀表达式转后缀 + 后缀表达式求值(完整可运行实现)
算法
phoenix@Capricornus2 小时前
从统计决策到贝叶斯估计
人工智能·算法·机器学习