字符串hash入門

 

簡單介紹一下字符串hash

相信你們對於hash都不陌生php

翻譯過來就是搞砸,亂搞的意思嘛html

 

hash算法普遍應用於計算機的各種領域,像什麼md5,文件效驗,磁力連接 等等都會用到hash算法node

在信息學奧賽中,hash算法主要應用於搜索狀態判重,字符串的比較等ios

 

hash的主要思想是:對於一個空間、時間需求較大的狀態,在必定錯誤率的基礎上進行狀態壓縮,下降其時間、空間的需求量算法

對於字符串hash來講,就是把一串字符串壓縮成一個hash值,方便咱們進行數據的處理數組

 

接下來咱們重點講一下字符串hash的實現方法app

實現方法

思想

在信息學奧賽中,使用最普遍的算法叫作:BKDR Hashide

它的核心思想是:優化

對於一個字符串,選取恰當的進制,將一個字符串看作是一個大整數atom

(衆人:***,你這是要讓咱們寫高精啊)

而後再對一個隨便什麼數取模就能夠啦

 

固然這個「恰當的進制」和「隨便什麼數」是有講究的

 

根據磚家的研究:

進制通常選擇大於字符串中的最大的字符且不含模數的值因子的數

好比說,若是你是對一串小寫字母作字符串hash,那麼131這個進制就是不錯的選擇

 

而「隨便什麼數」有三種方法

  1. 選擇兩個模數,判斷的時候只有兩個hash值都相同纔算相同
  2. 選擇一個大質數,像11111111111111111111111或者212370440130137957
  3. 用unsigned long long 天然溢出

注意:儘可能不要只用一個較小的質數,根據生日悖論,很容易被卡

 

代碼

代碼實現比較簡單

https://www.luogu.org/problemnew/show/3370

  • unsigned long long 天然溢出
 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<cmath>
 5 #include<cstring>
 6 #include<algorithm>
 7 #include<map>
 8 #define lli long long int 
 9 using namespace std;
10 const int MAXN=100001;
11 int seed=27;
12 void read(int &n)
13 {
14     char c='+';int x=0;bool flag=0;
15     while(c<'0'||c>'9'){c=getchar();if(c=='-')flag=1;}
16     while(c>='0'&&c<='9')x=x*10+c-48,c=getchar();
17     n=flag==1?-x:x;
18 }
19 char a[MAXN];
20 map<long long ,bool>happen;
21 int tot=0;
22 int main()
23 {
24     int n;
25     read(n);
26     for(int i=1;i<=n;i++)
27     {
28         unsigned long long base=1;
29         scanf("%s",a);
30         for(int j=0;j<strlen(a);j++)
31             base=base*seed+(a[j]);
32         if(!happen[base])
33             happen[base]=1,tot++;
34     }
35     printf("%d",tot);
36     return 0;
37 }
ull
  • 雙hash
 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #define ull unsigned long long 
 5 using namespace std;
 6 const int MAXN=2e7+10;
 7 const int mod1=19260817;
 8 const int mod2=19660813;
 9 const int seed=233;
10 const int seed2=133;
11 int n;
12 struct node
13 {
14     char s[1501];
15     int h1,h2,l;
16 }a[10001];
17 ull gethash1(char *s,int l)
18 {
19     ull ans=0;
20     for(int i=1;i<=l;i++)
21         ans=(ans*seed+(ull)s[i])%mod1;
22     return ans;
23         
24 }
25 ull gethash2(char *s,int l)
26 {
27     ull ans=0;
28     for(int i=1;i<=l;i++)
29         ans=(ans*seed2+(ull)s[i])%mod2;
30     return ans;
31 }
32 int hash1[MAXN],hash2[MAXN];
33 int main()
34 {
35     scanf("%d",&n);
36     for(int i=1;i<=n;i++)
37     {
38         scanf("%s",a[i].s+1);
39         a[i].l=strlen(a[i].s+1);
40     }
41     for(int i=1;i<=n;i++)
42     {
43         a[i].h1=gethash1(a[i].s,a[i].l)%mod1;
44         a[i].h2=gethash2(a[i].s,a[i].l)%mod2;
45     }
46     int ans=0;
47     for(int i=1;i<=n;i++)
48         if(hash1[a[i].h1]==0||hash2[a[i].h2]==0)
49             hash1[a[i].h1]=1,hash2[a[i].h2]=1,ans++;
50     printf("%d",ans);
51     return 0;
52 }
雙hash
  • 大質數hash
 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<cmath>
 5 #include<cstring>
 6 #include<algorithm>
 7 #include<map>
 8 #define lli long long int 
 9 using namespace std;
10 const int MAXN=100001;
11 const long long int mod=212370440130137957;
12 int seed=27;
13 void read(int &n)
14 {
15     char c='+';int x=0;bool flag=0;
16     while(c<'0'||c>'9'){c=getchar();if(c=='-')flag=1;}
17     while(c>='0'&&c<='9')x=x*10+c-48,c=getchar();
18     n=flag==1?-x:x;
19 }
20 char a[MAXN];
21 map<int,bool>happen;
22 int tot=0;
23 int main()
24 {
25     int n;
26     read(n);
27     for(int i=1;i<=n;i++)
28     {
29         long long base=1;
30         scanf("%s",a);
31         int la=strlen(a);
32         for(int j=0;j<la;j++)
33             base=(base*seed+(a[j]) )%mod;
34         if(!happen[base])
35             happen[base]=1,tot++;
36     }
37     printf("%d",tot);
38     return 0;
39 }
大質數

 

特別注意:在循環的時候,不要寫: for(int j=0;j<strlen(a);j++) !!!!由於strlen的複雜度是$O(n^2)$,數據稍微一大你就會被卡T

正確的姿式是把長度記錄下來 int la=strlen(a); for(int j=0;j<la;j++) 

 

經常使用技巧:區間hash值

在進行字符串hash的時候,咱們常常會用到某一段的hash值

這時候怎麼辦呢?

假設咱們已經獲得了$hash[1-r]$

那麼$hash[l-r]=hash[r]-seed^{r-l+1}*hash[l - 1]$(seed表示進制)

舉個例子

$hash[1]=a1$

$hash[2]=a1*base+a_2$

$hash[3]=(a_1*seed+a_2)*seed+a_3=a_1*seed^2+a_2*seed+a_3$

$hash[4]=[(a[1*seed+a_2)*seed+a_3]*seed+a_4=a1*seed^3+a2*seed^2+a_3*seed+a_4$

$hash[3-4]=hash[4]-base^{2}*hash[2]=a_3*seed+a_4$

很明顯能夠看出這個公式是對的

最好本身手寫一下,這樣才能體會到其中的奧妙

 

經典題目

洛谷 P3370 【模板】字符串哈希  

必作練手題,代碼已經在上面給出了

洛谷 P3375 【模板】KMP字符串匹配

這道題能夠用字符串hash水過

http://www.cnblogs.com/zwfymqz/p/7793347.html

洛谷P2264 情書

這道題理論上是能夠用字符串hash作的

可是我只水到90分,各位神犇能夠嘗試一下

http://www.cnblogs.com/zwfymqz/p/7355159.html

BZOJ 3555: [Ctsc2014]企鵝QQ

 略有難度

http://www.cnblogs.com/zwfymqz/p/7349658.html

洛谷P3823 蚯蚓排隊

其餘經典應用

如下內容來自遠航之曲大神,在此表示衷心的感謝

一、kmp

問題:給兩個字符串S1,S2,求S2是不是S1的子串,並求S2在S1中出現的次數

把S2 Hash出來,在S1裏找全部長度爲$|S2|$的子串,Hash比較。效率$O(|S1|)$

二、AC自動機

問題:給N個單詞串,和一個文章串,求每一個單詞串是不是文章串的子串,並求每一個單詞在文章中出現的次數。

把每個單詞hash成整數,再把文章的每個子串hash成整數,接下來只須要進行整數上的查找便可。

複雜度:$O(|A|2+|S|)$

用AC自動機能夠作到$O(|A|+|S|)$的複雜度,|S|是單詞串總長,$|A|$是文章長度

三、後綴數組

問題:給兩個字符串S1,S2,求它們的最長公共子串的長度。

將S1的每個子串都hash成一個整數,將S2的每個子串都hash成一個整數

兩堆整數,相同的配對,而且找到所表示的字符串長度最大的便可。

複雜度:$O(|S1|2+|S2|2)$

用後綴數組能夠優化到$O(|S|log|S|)$

四、馬拉車

問題:給一個字符串S,求S的最長迴文子串。

先求子串長度位奇數的,再求偶數的。枚舉迴文子串的中心位置,而後二分子串的長度,直到找到一個該位置的最長迴文子串,不斷維護長度最大值便可。

複雜度:$O(|S|log|S|)$

用manacher能夠作到$O(|S|)$的複雜度

五、擴展kmp

問題:給一個字符串S,求S的每一個後綴與S的最長公共前綴

枚舉每個後綴的起始位置,二分長度,求出每一個後綴與S的最長公共前綴。

複雜度:$O(|S|log|S|)$

用extend-kmp能夠作到$O(|S|)$的複雜度

 

總結

字符串hash是一個很是優秀的算法。

但願你們可以熟練的掌握&&運用

說不定它能夠在你寫不出正解的時候幫你得不少分呢?

相關文章
相關標籤/搜索