字符串哈希初探和模板题解析 (附算法解析和C++代码实现)

一. 哈希算法回顾

哈希函数: 将关键字映射成对应的地址的函数,记为 Hash(key) = Addr。

哈希冲突: 哈希函数可能会把两个或两个以上的不同关键字映射到同⼀地址,这种情况称为哈希冲
突。


二. 字符串哈希原理和代码实现

定义⼀个把字符串映射到整数的函数 hash,这就是字符串哈希。说⽩了,就是将⼀个字符串⽤⼀个整数表⽰。


1. 字符串哈希中的哈希函数
在字符串哈希中,有⼀种冲突概率较⼩的哈希函数,将字符串映射成 p 进制 数字:hash(s) = (i = 0 -> n - 1)Σ s[i] * (p ^ (n - i - 1)) (mod M)

其中,p 通常取质数 131 或者 13331。如果把哈希值定义为 unsigned long long 类型,在 C++ 中,溢出就会⾃动取模。

(你没有看错,字符串哈希就是背⼀个公式即可…)

但是,实际求哈希值时,我们⽤的是前缀哈希的思想来求,这样会和下⾯的多次询问⼦串哈希⼀致。


2. 前缀哈希数组
单次计算⼀个字符串的哈希值复杂度是 O(n)。如果需要多次询问⼀个字符串的⼦串的哈希值,每次重新计算效率⾮常低下。

⼀般利⽤ 前缀和思想 先预处理字符串中每个前缀的哈希值,这样的话每次就能快速求出⼦串的哈希
了。


3. 代码实现

#include <iostream>

typedef unsigned long long ULL;
const int N = 1e6 + 10, P = 13331;
char str[N];
int len;
ULL PreFix[N];//前缀哈希数组 
ULL p[N];//记录 p 的 i 次⽅ 
//处理前缀哈希数组以及 p 的 i 次⽅数组 

//处理哈希表
void init_hash()
{
    PreFix[0] = 0; p[0] = 1;
    for(int i = 1; i <= len; i++)
    {
    PreFix[i] = PreFix[i - 1] * P + str[i];
    p[i] = p[i - 1] * P;
    }
}

// 快速求得任意区间的哈希值 
ULL get_hash(int l, int r)
{
    return PreFix[r] - PreFix[l - 1] * p[r - l + 1];
}

如果题⽬只是简单的求单个字符串的哈希值:

typedef unsigned long long ULL;
const int N = 1e6 + 10;
int len;
char str[N];

ULL get_hash()
{
    ULL ret = 0;

    for(int i = 1; i <= len; i++) {
        ret = ret * p + str[i];
    }

    return ret;
}

三.例题详解

P3370 【模板】字符串哈希


P3370 【模板】字符串哈希

1. 题目描述

如题,给定 N N N 个字符串(第 i i i 个字符串长度为 M i M_i Mi,字符串内包含数字、大小写字母,大小写敏感),请求出 N N N 个字符串中共有多少个不同的字符串。


2. 输入格式

第一行包含一个整数 N N N,为字符串的个数。

接下来 N N N 行每行包含一个字符串,为所提供的字符串。


3. 输出格式

输出包含一行,包含一个整数,为不同的字符串个数。


4. 输入输出样例

输入

5
abc
aaaa
abc
abcc
12345

输出

4

5. 说明/提示

对于 100 % 100\% 100% 的数据: N ≤ 10000 N\leq 10000 N10000 M i ≈ 1000 M_i≈1000 Mi1000 M m a x ≤ 1500 Mmax\leq 1500 Mmax1500


解题思路


如题可知此题为字符串模板题,亦可使用 unordered_map 或者 map 直接解决问题。
直接模拟字符串哈希的实现流程即可


#include <iostream>
#include <string>
#include <algorithm>

typedef unsigned long long ULL;
const int N = 1e4 + 10, P = 131;
int n;
ULL hash[N];

//将字符串映射成一个哈希值
ULL get_hash(std::string str)
{
    ULL ret = 0;
    int n = str.size();
    for(int i = 0; i < n; i++) {
        ret = ret * P + (ULL)str[i];
    }
    return ret;
}

int main(void)
{
    std::cin >> n;

    for(int i = 1; i <= n; i++) {
        std::string str;
        std::cin >> str;

        hash[i] = get_hash(str);
    }

    //排序便于统计不重复数
    std::sort(hash + 1, hash + 1 + n);

    int ret = 0;
    for(int i = 1; i <= n; i++) {
        if(hash[i] != hash[i - 1]) {
            ret++;
        }
    }
    std::cout << ret << std::endl;

    return 0;
}

如有不足之处欢迎各位指出,感谢!


优快云
Github

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值