很有啟發(fā)的幾篇文章:
文章傳送門(mén):
文章一:KMP算法的Next數(shù)組詳解
文章二:從頭到尾徹底理解KMP
文章三:字符串匹配的KMP算法
首先說(shuō)說(shuō)字符串模式匹配問(wèn)題:
問(wèn)題描述:子串的定位操作通常稱(chēng)作串的模式匹配,即查找子串在主串中的位置蔚携。我們把主串稱(chēng)為S数初,子串稱(chēng)作模式串T伤锚,用 i 和 j 指示主串S和模式串T中當(dāng)前正待比較的字符位置羔杨。
算法基本思想:從主串S的第pos個(gè)字符起和模式的第一個(gè)字符比較之徘意,若相等岳服,則繼續(xù)逐個(gè)比較下個(gè)字符(pos值不變);若不相等,從主串的pos++個(gè)字符起再重新和模式串的第一個(gè)字符比較鲸沮。依次類(lèi)推档押,直到模式串T中的每個(gè)字符依次和主串S中的一個(gè)連續(xù)的字符序列相等澳盐,則匹配成功,函數(shù)值為主串里的匹配串第一個(gè)字符在主串中的序號(hào)令宿,若匹配不成功叼耙,函數(shù)值返回0.
上圖:
匹配過(guò)程如下:
1:pos=1,i=1;j=1;S[i]==T[j],繼續(xù)比較到i=6,j=6時(shí)S[6]!=T[6]
2:pos=2,i=2;j=1;S[i]!=T[j]
3:pos=3,i=3;j=1;S[i]!=T[j]
4:pos=4,i=4;j=1;S[i]==T[j],i=5;j=2;S[i]==T[j]一直到i=9;j=6時(shí)匹配成功,函數(shù)值為4.
數(shù)據(jù)結(jié)構(gòu)描述:
//求子串位置的定位函數(shù) Index(S,T,pos)
int Index(SString S,SString T,int pos){
//返回子串T在主串中第pos個(gè)字符之后的位置粒没。若不存在筛婉,則函數(shù)值為0.
//其中,T非空癞松,1<=pos<=StrLength(S)
i=pos;j=1;
while(i<=S[0]&&j<=T[0]){
if(S[i]==T[j]){//繼續(xù)比較后續(xù)字符
i++;
j++;
}
else{//指針后退重新開(kāi)始匹配
i=i-j+2;
j=1;
}
}
if(j>T[0])return i-T[0];
else return 0;
}//Index
代碼:
#include<bits/stdc++.h>
using namespace std;
int main(){
string S,T;//S是主串爽撒,T為模式串
cin>>S>>T;
int pos=0,flag=0,ans=-1;//flag==1,則匹配成功
while(1){
int i=pos;//i是主串的位置指針
if(S.size()-i<T.size()){
break;
}
for(int j=0;j<T.size();j++){//j是模式串的位置指針
if(S[i]==T[j]){
i++;
if(j==T.size()-1){
flag=1;
ans=pos+1;//S下標(biāo)是從零開(kāi)始响蓉,故ans在pos基礎(chǔ)上加1
break;
}
continue;
}
else{
pos++;
break;
}
}
if(flag==1){
break;
}
}
cout<<ans<<endl;
return 0;
}
KMP算法介紹:
這個(gè)算法是由高德納和沃恩·普拉特在1974年構(gòu)思硕勿,同年詹姆斯·H·莫里斯也獨(dú)立地設(shè)計(jì)出該算法,最終由三人于1977年聯(lián)合發(fā)表枫甲。這種算法被稱(chēng)為 Knuth-Morris-Pratt 操作源武,簡(jiǎn)稱(chēng)為 “KMP算法”,常用于在一個(gè)文本串S內(nèi)查找一個(gè)模式串P 的出現(xiàn)位置想幻。
上圖:
kmp過(guò)程
1:i=1;j=1.繼續(xù)逐一比較直到i=6;j=6時(shí)S[i]!=L[j]
2:i=4;j=1.繼續(xù)逐一比較直到i=9;j=6匹配成功
顯然软能,KMP算法改進(jìn)之處在于,每當(dāng)一趟匹配過(guò)程中出現(xiàn)字符比較不等時(shí)举畸,不需回溯i指針查排,而是利用已經(jīng)得到的“部分匹配”的結(jié)果將模式向右”滑動(dòng)“盡可能遠(yuǎn)的一段距離后繼續(xù)進(jìn)行比較。那么問(wèn)題來(lái)了抄沮,模式串向右“滑動(dòng)”的可行距離多遠(yuǎn)跋核,即當(dāng)主串中第 i 個(gè)字符與模式中第 j 個(gè)字符比較不等時(shí)岖瑰,主串中第 i 個(gè)字符應(yīng)與模式串中哪個(gè)字符進(jìn)行比較?(注意, i 不回溯砂代。)
由此引出數(shù)組:
令;則
表明當(dāng)模式串中第
個(gè)字符與主串第
個(gè)字符“失配”時(shí)蹋订,在模式串中需重新和主串中該字符進(jìn)行比較的字符的位置。即
就是模式串向右“滑動(dòng)”的距離刻伊。所以問(wèn)題在于如何求
數(shù)組露戒?
引出前綴和后綴:假設(shè)主串'',模式串為'
'捶箱,并假設(shè)此時(shí)應(yīng)與模式串中第
個(gè)字符繼續(xù)比較智什,則模式串中前
個(gè)字符的子串必須滿(mǎn)足關(guān)系式
,且不可能存在
滿(mǎn)足下列關(guān)系式
''='
'
已經(jīng)得到的“部分匹配”的結(jié)果是
''='
'
由式和式
推得下列等式
''='
'
式子用文字描述:
模式串中存在兩個(gè)相同的子串,該子串以模式串的第一個(gè)字符為首字符丁屎,長(zhǎng)度為荠锭,
就是"前綴"和"后綴"的最長(zhǎng)的共有元素的長(zhǎng)度。"前綴"指除了最后一個(gè)字符以外晨川,一個(gè)字符串的全部頭部組合证九;"后綴"指除了第一個(gè)字符以外,一個(gè)字符串的全部尾部組合共虑。
舉例:
- "A"的前綴和后綴都為空集愧怜,共有元素的長(zhǎng)度為0;
- "AB"的前綴為[A]妈拌,后綴為[B]叫搁,共有元素的長(zhǎng)度為0;
- "ABC"的前綴為[A, AB]供炎,后綴為[C,BC],共有元素的長(zhǎng)度0疾党;
- "ABCD"的前綴為[A, AB, ABC]音诫,后綴為[D,CD,BCD],共有元素的長(zhǎng)度為0雪位;
- "ABCDA"的前綴為[A, AB, ABC, ABCD]竭钝,
后綴為[A,DA,CDA,BCDA],共有元素為"A"雹洗,長(zhǎng)度為1香罐;
- "ABCDAB"的前綴為[A, AB, ABC, ABCD, ABCDA],后綴為 [B,AB,DAB,CDAB,BCDAB]时肿,共有元素為"AB"庇茫,長(zhǎng)度為2;
- "ABCDABD"的前綴為[A, AB, ABC, ABCD, ABCDA, ABCDAB]螃成,后綴為[D,BD,ABD,DABD,CDABD,BCDABD]旦签,共有元素的長(zhǎng)度為0查坪。
求數(shù)組的代碼:
#include<bits/stdc++.h>
using namespace std;
const int maxn=100;
int nxt[maxn];
void getNext(string p){
// 初始條件
int j=0;
int k=-1;
nxt[0]=-1;
// 根據(jù)已知的前j位推測(cè)第j+1位
while(j<p.size()-1){//p[k]表示前綴,p[j]表示后綴
if(k==-1||p[j]==p[k])nxt[++j]=++k;
else k=nxt[k];
}
}
int main(){
string p;
cin>>p;
getNext(p);
for(int i=0;i<p.size();i++)
cout<<nxt[i]<<' ';
cout<<endl;
return 0;
}
運(yùn)行:
改進(jìn):
void getNext(string p){
// 初始條件
int j=0;
int k=-1;
nxt[0]=-1;
// 根據(jù)已知的前j位推測(cè)第j+1位
while(j<p.size()-1){//p[k]表示前綴宁炫,p[j]表示后綴
if(k==-1||p[j]==p[k]){
++j;
++k;
if(p[j]!=p[k])nxt[j]=k;//優(yōu)化
else nxt[j]=k;
}
else k=nxt[k];
}
}
改進(jìn)原因:當(dāng)p[j] != s[i] 時(shí)偿曙,下次匹配必然是p[ next [j]] 跟s[i]匹配,如果p[j] = p[ next[j] ]羔巢,必然導(dǎo)致后一步匹配失斖洹(因?yàn)閜[j]已經(jīng)跟s[i]失配,然后你還用跟p[j]等同的值p[next[j]]去跟s[i]匹配竿秆,很顯然启摄,必然失配),所以不能允許p[j] = p[ next[j ]]袍辞。如果出現(xiàn)了p[j] = p[ next[j] ]咋辦呢鞋仍?答案是需要再次遞歸,即令next[j] = next[ next[j] ]搅吁。
如圖:
!=
注意
==
若不優(yōu)化威创,此時(shí)應(yīng)該找到nxt[j],此時(shí)j==3,
因此此nxt[3]==1;而p[nxt[3]]==b;是無(wú)效操作谎懦。
完整版算法
#include<bits/stdc++.h>
using namespace std;
const int maxn=100;
int nxt[maxn];
string s,p;
void getNext(string p){
// 初始條件
int j=0;
int k=-1;
nxt[0]=-1;
// 根據(jù)已知的前j位推測(cè)第j+1位
while(j<p.size()-1){//p[k]表示前綴肚豺,p[j]表示后綴
if(k==-1||p[j]==p[k]){
++j;
++k;
if(p[j]!=p[k])nxt[j]=k;//優(yōu)化
else nxt[j]=k;
}
else k=nxt[k];
}
}
int kmp(int lens,int lenp){
int i=0,j=0,ans=-1;//如果最后ans==1,則匹配失敗
while(i<lens&&j<lenp){
if(j==-1||s[i]==p[j]){
i++;
j++;
}
else j=nxt[j];
if(j==lenp)return ans=i-lenp+1;//習(xí)慣上第一個(gè)字符下標(biāo)為1
}
return ans;
}
int main(){
//string s,p;
cin>>s>>p;
memset(nxt,-1,sizeof(nxt));
getNext(p);
int lens=s.size();
int lenp=p.size();
int ans=kmp(lens,lenp);
cout<<ans<<endl;
return 0;
}