1. 程式人生 > 實用技巧 >雜湊演算法實現字串匹配

雜湊演算法實現字串匹配

對於匹配字串m和目標字串n,最樸素的思想就是對於每個起始位置都o(m)地直接比較字元是否相同,最後時間複雜度為O(m*n)這樣的時間複雜度在大多數時候都很不樂觀,因此需要一些技巧降低時間複雜度。

雜湊演算法:

我們可以將一段字串對映為數值然後比較數值大小,若相等則匹配成功。這是理想情況下的,要達到這樣的效果需要雜湊演算法具備一定的抗碰撞性(即不同的字串不會對映到同一數值)。在這個前提下,要是計算每個長度為m的字串的雜湊值,最終複雜度必然還是O(m*n),這裡可以用滾動雜湊的方法優化。

對於匹配字串 s=C₁C₂C₃.....C⒨ 雜湊值H=C₁*B^(m-1)+C₂*B^(m-2)+C₃*B^(m-3)+...+C⒨ 其中B取一個非常大素數如(1e9+7),B^(m-1)表示B的m-1次方

對於目標字串ss=A₁A₂A₃....A⒩ 已知A₁開始長度為m的字串的雜湊值為h , 要得到A₂開始長度為m的字串的雜湊值只需

h′ = h*B-A₁*B^m+D      其中D為第(m+1)位字元, 就可以在O(n)的時間複雜度內實現字串匹配

例題:POJ 3690

先上程式碼:

 1 #include<iostream>
 2 #include<algorithm>
 3 #include<string>
 4 #include<set>
 5 #include<vector>
 6 #include<queue>
 7
#include<stack> 8 #include<map> 9 #include<cmath> 10 #include<string> 11 using namespace std; 12 typedef unsigned long long ull; 13 char s[1010][1010]; 14 char pat[110][55][1010]; 15 ull hash_[1010][1010], temp[1010][1010]; 16 const ull B = 1e9 + 7, A = 9973; 17 int N, M, P, Q, T; 18 void
comput_hash(char a[][1010], int n, int m) 19 { 20 int i, j, k; 21 ull e = 0, t1 = 1, t2 = 1; 22 for (i = 1; i <= Q; i++) 23 t1 *= A; 24 for (i = 1; i <= n; i++) 25 { 26 e = 0; 27 for (j = 1; j <= Q; j++) 28 e = e * A + a[i][j]; 29 for (j = 1; j + Q - 1 <= m; j++) 30 { 31 temp[i][j] = e; 32 if (j + Q <= m) 33 e = e * A - a[i][j] * t1 + a[i][j + Q]; 34 } 35 } 36 37 e = 0; 38 for (i = 1; i <= P; i++) 39 t2 *= B; 40 for (j = 1; j + Q - 1 <= m; j++) 41 { 42 e = 0; 43 for (i = 1; i <= P; i++) 44 e = e * B + temp[i][j]; 45 for (i = 1; i + P - 1 <= n; i++) 46 { 47 hash_[i][j] = e; 48 if (i + P <= n) 49 e = e * B - temp[i][j] * t2 + temp[i + P][j]; 50 } 51 } 52 } 53 int main() 54 { 55 int i, j, k; 56 int cnt = 0, ans; 57 while (~scanf("%d%d%d%d%d", &N, &M, &T, &P, &Q)) 58 { 59 if (N == 0 && M == 0 && P == 0 && Q == 0 && T == 0) 60 break; 61 for (i = 1; i <= N; i++) 62 scanf("%s", s[i] + 1); 63 for (k = 1; k <= T; k++) 64 for (i = 1; i <= P; i++) 65 scanf("%s", pat[k][i] + 1); 66 67 multiset<ull>ml; 68 for (i = 1; i <= T; i++) 69 { 70 comput_hash(pat[i], P, Q); 71 ml.insert(hash_[1][1]); 72 } 73 74 comput_hash(s, N, M); 75 for (i = 1; i + P - 1 <= N; i++) 76 for (j = 1; j + Q - 1 <= M; j++) 77 ml.erase(hash_[i][j]); 78 79 ans = T - ml.size(); 80 printf("Case %d: %d\n", ++cnt, ans); 81 } 82 return 0; 83 }

對於這道題,我們可以先只看一個維度,對於一個維度的匹配就是簡單的板子題,就像之前所說進行匹配即可,在拓展到二維時,應該考慮行列均相同。

按一個維度的思想,我們要保證一個字元相同,然後是一段字元相同。

這裡我們可以將每一行的一段字串的雜湊值視為“一個字元”’(雜湊演算法需要足夠抗碰撞允許我們這麼做)

然後我們繼續在列方向上進行匹配即可。

在程式碼中 用unsigned long long 來存雜湊值能讓資料自然溢位,若不用ull,則應在資料溢位前模上一個足夠大的素數,

以B=1e9+7 作為行的基數 A=9973作為列的基數

先計算每個匹配模式的hash值,放入multiset裡,hash[ i ][ j ]表示以從 i 到 i+P ,j 到 j+Q的匹配模式的雜湊值

再計算匹配物件的,然後將匹配物件中出現過的hash值從multiset裡剔除,

此時在multiset裡剩下的就是未在匹配物件出現過的匹配模式的個數

那麼答案就是用匹配模式的個數 T 減去multiset裡元素個數

參考書籍————《挑戰程式設計競賽》