KMP算法,字符串算法,在CSDN博客這裏,有很多人寫的很好,非常地詳細,讓人自嘆不如。然而我很懶,不想看太多文字,只想通俗地理解這KMP,所以寫下這篇文章,文章很簡陋,只是基本搞懂KMP的原來,沒有去深究它,如果這文章對大家沒有幫助,請忽略。
1、字符串hash進階
字符串hash是指將一個字符串s映射爲一個整數,使得該整數可以儘可能唯一地代表字符串s。那麼在一定程度上,如果兩個字符串轉換成地整數相等,就可以認爲這兩個字符串相同。
直達鏈接:原題地址
#include <iostream>
#include<string>
#include<vector>
#include<algorithm>
using namespace std;
const int Mod = 1e9+7;
const int p = 1e7+19;
vector<int>arr;
long long hashfunc(string str){
long long Hash = 0;
for(int i=0;i<str.size();i++){
Hash = (Hash*p + str[i] - 'a')%Mod;
}
return Hash;
}
int main(){
string str;
int n;
cin>>n;
for(int i=0;i<n;i++)
{
cin>>str;
long long Hash=hashfunc(str);
arr.push_back(Hash);
}
sort(arr.begin(),arr.end());///字符串排序
int ans = 0;
for(int i=0;i<arr.size();i++)
if(i==0||arr[i]!=arr[i-1]){
ans++;
}
cout<<ans<<endl;
return 0;
}
2、KMP算法
2.1 暴力匹配算法
在瞭解KMP算法之前我們先了解一下一個暴力匹配算法,對於字符串的匹配問題我們很多人都可能一開始想到的是暴力,沒錯!!!如果題目數據量不大,可以選擇這個算法:
暴力匹配核心代碼:
int stringMatch(char *text,char *pattern){
int tlen = strlen(text);
int plen = strlen(pattern);
int i=0;
int j=0;
while(i<tlen&&j<tlen){
if(text[i]==pattern[j]){
i++;
j++;
}
else{
i=i-j+1;
j=0;
}
if(j==plen) return i-j;///匹配成功,返回模式串在主串中的位置
else return -1;///匹配失敗
}
}
暴力匹配算法的複雜度爲O(nm),所以如果數據量比較大的話,這種算法就無法接受了。
2.2 KMP算法
b站上講得比較通俗易懂的兩個視頻:
Knuth-Morris-Pratt字符串查找算法,簡稱爲"KMP算法",這個算法由Donald Knuth、Vaughan Pratt、James H. Morris三人共同發現的。這算法用於判斷字符串pattern是否爲text的字串。
在學KMP算法之前,我們先來學習一個next數組,這個數組對於實現KMP算法非常重要,等同於求出next數組就差不多可以實現字符串匹配了。如:子串s[0…i],其長度爲k+1的前綴和後綴分別爲s[0…k]和s[i-k…i]。nexi[i]就是使子串s[0…i]的前綴s[0…k]等於後綴s[i-k…i]的最大的k(最大公共前後綴的長度)。
注意:next[i]就是最大公共前後綴中地前綴的最後一位的小標,且最大公共前後綴長度必須小於本身
雖然上面圖讓我們明白求next數組的基本原來,但是我們在程序中怎麼去編代碼一一地找出最大公共前後綴地長度呢?
第①步:j=0,i=1→s[1] ! = s[0],回溯到 j=0→next[1]=j=0。
第②步:當前j=0,i=2→回溯s[2]!=s[1],s[2]==s[0],跳出循環,j=0→next[2]=j+1=1。
第③步:當前j=1,i=3→s[3]!=s[1],s[3]!=s[0],j=0→next[3]=j=0。
第④步:當前j=0→s[4]==s[0],j=0跳出循環→next[4]=j+1=1。
第⑤步:當前j=1→s[5]==s[1],j=1跳出循環→next[5]=j+1=1+1=2。
第⑥步:當前j=2→s[6]==s[2],j=2跳出循環→next[6] = j+1 = 2+1=3。
第⑦步:當前j=3→s[7]!=s[3],s[7]==s[0],j=0,跳出循環→next[7]=j+1=1。
獲取數組next的值:
void getNext(char s[]){
int len = strlen(s);
int j = 0;
next[0] = 0;
for(int i = 1;i<len;i++) {///由於第一個字符肯定next爲0(因爲最大公共前後綴不能爲本身),所以從1開始
while(s[i]!=s[j]&&j!=0) j = next[j];///j=0跳出循環
if(s[i]==s[j]) j++;
next[i] = j;
}
}
kmp算法實現:
bool kmp(char *text,char *pattern){
int n = strlen(text);
int m = strlen(pattern);
getNext(pattern);
int j=0;
for(int i=0;i<n;i++){
while(j!=0&&text[i]!=pattern[j]) j = next[j];
if(text[i]==pattern[j]) j++;
if(j==m-1) return true;
}
return false;
}
3、KMP算法的擴展
3.1 BM算法
KMP的匹配是從模式串的開頭開始匹配的,而1977年,德克薩斯大學的Robert S. Boyer教授和J Strother Moore教授發明了一種新的字符串匹配算法:Boyer-Moore算法,簡稱BM算法。該算法從模式串的尾部開始匹配,且擁有在最壞情況下O(N)的時間複雜度。比KMP算法的實際效能高。
BM算法定義了兩個規則:
☞ 壞字符規則:
第①種情況: 模式串中不存在S字符:S與N不匹配,S爲壞字符。
所有模式串字符移動模式串長度位:
第②種情況: 模式串與文本串都存在A字符,A與N不匹配,A爲壞字符。
移動到模式串中從右到左的第一個A(最右邊的公共字符)。
☞ 好後綴規則:
Y與A不匹配,需要移動:
移動方法一:按照壞字符規則模式串移動了3步。
移動方法二:按照“好後綴原則”,模式串移動了6步。模式串有公共前後綴,直接移動到前綴與當前文本串公共字符對應位置
3.2 Sunday算法
Subday算法比BM算法效能更快,Sunday算法由Daniel M.Sunday在1990年提出,它的思想跟BM算法很相似:Sunday算法是從前往後匹配,在匹配失敗時關注的是文本串參加匹配的最末位字符的下一個字符。如果該字符沒有在匹配中出現則直接跳過,即移動步長=模式串長度+1;否則,同BM算法一樣移動步長模式串中最右端的該字符到末尾的距離+1。
4、參考資料
1.<a href = ''https://blog.csdn.net/v_july_v/article/details/7041827#>從頭到尾徹底理解KMP(2014年8月22日版)
2.《算法筆記》
3.http://blog.chinaunix.net/uid-22237530-id-1781825.html