算法学习->字符串hash

一、什么是哈希?字符串哈希有哪些?

Hash,一般翻译做“散列”,也有直接音译为“哈希”的,就是把任意长度的输入(又叫做预映射pre-image)通过散列算法变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来确定唯一的输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。

所有散列函数都有如下一个基本特性:如果两个散列值是不相同的(根据同一函数),那么这两个散列值的原始输入也是不相同的。这个特性是散列函数具有确定性的结果。但另一方面,散列函数的输入和输出不是一一对应的,如果两个散列值相同,两个输入值很可能是相同的,但不绝对肯定二者一定相等(可能出现哈希碰撞)。

字符串hash,即把每个字符串hash为一个数字,对数字进行比对。

下面,我来演示求一个字符串的hash值:

1、要求:现在我们希望找到一个hash函数,使得每一个字符串都能够映射到一个整数上,比如hash[i]=(hash[i-1]*p+idx(s[i]))%mod

2、字符串:abc,bbc,aba,aadaabac,字符串下标从0开始

3、映射:先把a映射为1,b映射为2,c->3,d->4,即idx(a)=1, idx(b)=2, idx(c)=3,idx(d)=4;

4、取值:假设我们取p=13 ,mod=101

5、做法:
先把abc映射为一个整数
hash[0]=1,表示 a 映射为1(注意点)
hash[1]=(hash[0]*p+idx(b))%mod=15,表示 ab 映射为 15
hash[2]=(hash[1]*p+idx(c))%mod=97
这样,我们就把 abc 映射为 97 这个数字了,即hash[“abc”]=97。

•用同样的方法,我们可以把bbc,aba,aadaabac都映射到一个整数
•用同样的hash函数,得到如下结果
• abc -> 97,即hash[“abc”]=97
• bbc -> 64,即hash[“bbc”]=64
• aba -> 95,即hash[“aba”]=95
• aadaabac -> 35,即hash[“aadaabac”]=35

•那么,我们发现,这是一个字符串到整数的映射
•这样子,我们就可以记录下每个字符串对应的整数,当下一次出现了一个已经出现的字符串时,查询整数是否出现过,就可以知道 字符串是否重复出现。
•现在要判断两个字符串是否一致,怎么办呢?直接用它们的hash值判断即可,若hash值一致,则认为字符串一致;若hash值不一致,则认为是不同的字符串。
•我们要判断两个字符串是否一致,没有那么麻烦,直接先判断长度是否一致,然后再判断每个对应的字符是否一致即可。
•但,如果要判断多个字符串里有多少个不同的字符串,怎么办呢?
•两两字符串都进行比较?时间复杂度太高
•把每个字符串hash成一个整数,然后把所有整数进行一个去重操作,即可知道答案了。
当遇到冲突时,我们可以想办法调整p和mod,使得冲突概率减到小之又小。我们一般认为p和mod一般取素数,p取一个较大的素数即可(6位到8位),mod取一个大素数,比如1e9+7,或者1e9+9。

如何求一个子串的hash值?
•在之前,我们求出了hash[i],表示第i个前缀的hash值。现在怎么求出每个子串的
hash值呢?

•我们看下hash的公式:
• hash[i]=(hash[i-1]*p+idx(s[i]))%mod
•这表示第 i 个前缀的hash值,是一个hash的前缀和。
•hash[i]=(hash[i-1]*p+idx(s[i]))%p;
•那么,我要求S[l…r]这个子串的hash值
• hash[l..r]=(hash[r]-hash[l-1]*(p^(r-1+1)))%mod(假设字符串下标从1开始)
•但注意下取模时候的问题!
•hash[l..r]=(hash[r]-hash[l-1]*(p^(r-1+1)))%mod
• hash[l..r]是不是可能有负数?
•怎么办呢?当得到的hash[l..r]<0的时候,hash[l..r]+=mod,就好啦。
•这样就可以保证每个子串的hash值在[0, mod-1]的范围内,准确地用hash值来处理字符串

二、字符串哈希的三种映射函数

1.自动取模

     unsigned long long hash[N];
     hash[i]=hash[i-1]*p(自动取模)

解释:
unsigned long long hash[N];

定义一个unsigned long long类型的变量,它的范围是在[0, 2^64) 内,这就相当于,当数超不过2^64-1后,它会溢出!这就相当于一个数模2^64的过程。

那么hash函数可以理解为:

   hash[i]=(hash[i-1]*p)%(2^64)

P取一个大素数,一般习惯取1e9+7或1e9+9

安全指数:三星(所以并不是很安全)

2.字符串字符

hash[i]=(hash[i-1]*p+idx(s[i]))%mod

解释:
这个之前已经提到过了。

hash[i]=(hash[i-1]*p+idx(s[i]))%mod

p取一个6到8位的素数,mod取一个大素数,一般取1e9+7或1e9+9
安全指数:四星 (还可以)

3.双hash

     hash1[i]=(hash1[i-1]*p+idx(s[i]))%mod1
     hash2[i]=(hash2[i-1]*p+idx(s[i]))%mod2
     pair<hash1,hash2>表示一个字符串!

double hash
即取两个mod值,mod1和mod2
hash1[i]=(hash1[i-1]*p+idx(s[i]))%mod1

hash2[i]=(hash2[i-1]*p+idx(s[i]))%mod2

mod1一般取1e9+7,mod2一般取1e9+9为什么这么取?

1000000007和1000000009是一对孪生素数,取它们,冲突的概率极低!

安全指数:五星!(非常稳!)

小结:可以这么说,hash某种程度上就是乱搞,把hash函数弄的越没有规律越好,使得冲突的概率小到大部分数据都卡不掉。
•如果你开心,你想triple hash,ultra hash,rampage hash… 都没有问题!
但请注意,hash的维度越高,耗时越高,耗内存越大!一般情况下,single hash可以被hack掉,但double hash极难被hack掉, 用double hash足以解决问题

三、练习题及AC代码

Many people like to solve hard puzzles some of which may lead them to madness. One such puzzle could be finding a hidden prime number in a given text. Such number could be the number of different substrings of a given size that exist in the text. As you soon will discover, you really need the help of a computer and a good algorithm to solve such a puzzle.
Your task is to write a program that given the size, N, of the substring, the number of different characters that may occur in the text, NC, and the text itself, determines the number of different substrings of size N that appear in the text.

As an example, consider N=3, NC=4 and the text “daababac”. The different substrings of size 3 that can be found in this text are: “daa”; “aab”; “aba”; “bab”; “bac”. Therefore, the answer should be 5.

Input

The first line of input consists of two numbers, N and NC, separated by exactly one space. This is followed by the text where the search takes place. You may assume that the maximum number of substrings formed by the possible set of characters does not exceed 16 Millions.

Output

The program should output just an integer corresponding to the number of different substrings of size N found in the given text.

Sample Input

3 4
daababac

Sample Output

5

Hint

Huge input,scanf is recommended.

AC代码:

#include<iostream>
#include<cstring>
#include<stdio.h>
#include<string.h>
using namespace std;

int has[16000003],cnt=0,vis[500];
char str[16000003];
int n,nc,ans=0;

int main(){
    while(~scanf("%d%d%s",&n,&nc,str)){
        ans=0;cnt=0;
        int le=strlen(str);
//      memset(has,0,sizeof(has));
//      memset(vis,0,sizeof(vis));
        for(int i=0;i<le;i++){
            if(vis[str[i]]==0){
                vis[str[i]]=cnt++;
            }
        }
        for(int i=0;i<=le-n;i++){
            int sum=0;
            for(int j=0;j<n;j++){
                sum=sum*cnt+vis[str[j+i]];
            }
            if(has[sum]==0){
                has[sum]=1;
                ans++;
            }
        }
        printf("%d\n",ans);
    }
    return 0;
} 

2、 魔咒词典

哈利波特在魔法学校的必修课之一就是学习魔咒。据说魔法世界有100000种不同的魔咒,哈利很难全部记住,但是为了对抗强敌,他必须在危急时刻能够调用任何一个需要的魔咒,所以他需要你的帮助。

给你一部魔咒词典。当哈利听到一个魔咒时,你的程序必须告诉他那个魔咒的功能;当哈利需要某个功能但不知道该用什么魔咒时,你的程序要替他找到相应的魔咒。如果他要的魔咒不在词典中,就输出“what?”

Input

首先列出词典中不超过100000条不同的魔咒词条,每条格式为:

[魔咒] 对应功能

其中“魔咒”和“对应功能”分别为长度不超过20和80的字符串,字符串中保证不包含字符“[”和“]”,且“]”和后面的字符串之间有且仅有一个空格。词典最后一行以“@END@”结束,这一行不属于词典中的词条。
词典之后的一行包含正整数N(<=1000),随后是N个测试用例。每个测试用例占一行,或者给出“[魔咒]”,或者给出“对应功能”。

Output

每个测试用例的输出占一行,输出魔咒对应的功能,或者功能对应的魔咒。如果魔咒不在词典中,就输出“what?”

Sample Input

[expelliarmus] the disarming charm
[rictusempra] send a jet of silver light to hit the enemy
[tarantallegra] control the movement of one’s legs
[serpensortia] shoot a snake out of the end of one’s wand
[lumos] light the wand
[obliviate] the memory charm
[expecto patronum] send a Patronus to the dementors
[accio] the summoning charm
@END@
4
[lumos]
the summoning charm
[arha]
take me to the sky

Sample Output

light the wand
accio
what?
what?

AC代码:

#include<stdio.h> 
#include<map>
#include<string.h>
using namespace std;
typedef unsigned long long ull;
typedef unsigned int ui;
const int nmax = 150 ;
const int INF = 0x3f3f3f3f;
const ui MOD1 = 1e9 + 9;
const ui MOD2 = 1e9 + 9;
const ui p = 131;
int n, mcnt, fcnt;
char str[105];
char mis[100005][22], fun[100005][82];
map<ui, int> mp1, mp2;

inline ui hash1(char s[]) {
    int len = strlen(s);
    ui ans = s[0];
    for (int i = 1; i < len; ++i) ans = ((ans * p) + s[i] ) ;
    return ans;
}
inline ui hash2(char s[]) {
    int len = strlen(s);
    ui ans = s[0];
    for (int i = 1; i < len; ++i) ans = ((ans * p) + s[i] ) ;
    return ans;
}
int main() {
    while (gets(str)) {
        if (str[0] == '@') break;
        int len = strlen(str), pos;
        for (int i = 0; i < len; ++i) if (str[i] == ']') {pos = i; str[pos] = '\0'; break;}
        strcpy(mis[mcnt], str + 1); strcpy(fun[fcnt], str + pos + 2);
        mp1[hash1(mis[mcnt])] = fcnt;
        mp2[hash2(fun[fcnt])] = mcnt;
        fcnt++; mcnt++;
    }
    scanf("%d", &n); getchar();
    char str[100];
    for (int i = 1; i <= n; ++i) {
        gets(str);
        if (str[0] == '[') {
            int len = strlen(str); str[len - 1] = '\0';
            int ans = hash1(str + 1);
            if (mp1[ans] == 0) printf("what?\n");
            else printf("%s\n", fun[mp1[ans]]);
        } else {
            int ans = hash2(str);
            if (mp2[ans] == 0) printf("what?\n");
            else printf("%s\n", mis[mp2[ans]]);
        }
    }
    return 0;
}

更多练习题:
https://vjudge.net/contest/242212#overview

解释:以上理论讲解大部分来自别的博客,我只是做下学习总结。
原文出自:https://www.cnblogs.com/–ZHIYUAN/p/7445842.html

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值