KMP模版
字符串實在是太多圖片了,所以為了保存我的筆記,寫博客就是最好的辦法
1.KMP模版(出自caioj,但是現在不可以注冊,所以我就把題面放上來)
1177: [視頻]KMP模版:子串是否出現(元問題 by scy)
時間限制: 1 Sec??內存限制: 256 MB提交: 686??解決: 455
[提交] [狀態] [討論版] [命題人:admin]
題目描述
【題意】有兩個字符串SA和SB,SA是母串,SB是子串,問子串SB是否在母串SA中出現過。
如果出現過輸出第一次出現的起始位置和結束位置,否則輸出"NO"
【輸入文件】
第一行SA(1 <= 長度 <= 100 0000)
第二行SB(1 <= 長度 <= 1000)
【輸出文件】
如果SB在SA中出現過輸出第一次出現的起始位置和結束位置,否則輸出"NO"
【樣例1輸入】
aaaaabaa
aab
【樣例1輸出】
4 6
【樣例2輸入】
aaaaabaa
aax
【樣例2輸出】 NO
我們第一個想到的一定是樸素算法,就是我們在sa長度每一個枚舉sb的長度,看看有沒有相同的,但是很遺憾,因為數據10萬,O(nm)的時間復雜度怎么可能不炸呢?
所以我們就要用到一個很高級的東西就是KMP算法,這個算法是專門處理這些尋找字符串配對的一個算法,很好理解也很好實現,所以看一下我們下面的解釋
我們需要定義一個p數組,這個p數組就是KMP模版題的最關鍵的地方
?
p[ ]是記錄sb[ ]當中從后面開始往前延伸幾個字符,可以與前面開始往后延伸幾個字符相匹配,
就是字符的后綴和字符的前綴進行匹配,僅僅與sb[ ]有關 記住:僅僅與sb[ ]有關
那么這個這么抽象的p數組到底是干什么用的呢?
因為我們是把前后匹配的,每一個sb的位置都要匹配,所以我們如果要找第i個位置的 p[i]的時候,我們可以選擇繼承 p[i-1]的,繼承分為兩種?
- 第一種就是如果我們搜索到的前綴的后一個等于我們的i,那么我們的p[i]等于p[i-1]+1
- 第二種就是不等于的,我重點講一下第二種情況
?這張圖非常的重要,(圖片看不清楚,一定要下載下來看清楚顏色的范圍)因為他非常清晰的描述了我們KMP算法高效率的實現過程,為了方便,我們先定義 j=p[i-1]
如果找不到的話,我們就將j繼承為 p[j]也就是上面圖中的 p[p[i-1]]的位置,然后按照一步一步往下,我們就可以找到和i相等或者根本找不到相等的,
所以就是這樣
?那么我相信到這里,大佬們已經不屑于看了,剩下就是代碼的實現
(注釋版,想要看代碼理解的實現就打開這個)
1 /*樸素算法:在sa字符串當中按照sb的長度枚舉,長度相同且字符相同的時候,記錄下來,時間復雜度O(nm)*/ 2 #include<cstdio> 3 #include<cstring> 4 #include<cstdlib> 5 #include<algorithm> 6 #include<cmath> 7 #include<iostream> 8 using namespace std; 9 char sa[1000010],sb[1010]; 10 int p[1010]; 11 /*p[]是記錄sb[]當中從后面開始往前延伸幾個字符,可以與前面開始往后延伸幾個字符相匹配, 12 就是字符的后綴和字符的前綴進行匹配,僅僅與sb[]有關*/ 13 int main() 14 { 15 scanf("%s%s",sa+1,sb+1); 16 int lena=strlen(sa+1),lenb=strlen(sb+1); 17 p[1]=0;/*p數組初始化*/ 18 for(int i=2;i<=lenb;i++) 19 { 20 int j=p[i-1];/*偷懶操作,如果前面一個人找到了,然后我們現在要搜索的,與前面匹配完的后一個相同,那么就很好了*/ 21 while(j>0 && sb[i]!=sb[j+1]) j=p[j];/*這個就是p[]的重要性,不能直接繼承前面的狀態的話,就進行一個很神奇的操作 22 |1--la----lb--|2-|6------|3--lc----ld--|4-|5---- 23 (這是一個很抽象的圖,l5表示我們當前要匹配的,l3-l4是上一個匹配好的,l1-l2是和上一個匹配的前綴) 24 然后我們發現l5!=l6,所以不能繼承前面的狀態,那就意味著要重新搜索?不,我們可以把j定義到l2這個位置,也就是p[j], 25 為什么呢?因為我們把l2作為我們要搜索匹配的最后一個數,然后往前搜索,顯然: 26 l1~la=lb~l2=l3~lc=ld~l4,但是我們需要的只是l1~la=ld~l4,因為他們相同,并且他們的下一個分別相同,那么我們的i就匹配完了, 27 就可以退出記錄答案了,但是如果不匹配,就繼續將j定義到la這個位置,讓他搜索*/ 28 if(sb[i]==sb[j+1]) p[i]=j+1; else p[i]=0; /*如果相同,就直接記錄答案,否則沒有答案(退出while之后才會來到這里)*/ 29 } 30 int st,ed,j=0;/*j表示匹配成功的個數*/ 31 for(int i=1;i<=lena;i++)/*這個就是關于我們匹配好的p[]要怎么運用?*/ 32 { 33 /* 34 ------------|a--|5-|6--|b-l1------- sa 35 |c--|3-|4--|d-l2-- sb 這里表示兩個字符串,然后我們從1開始枚舉lena,發現有相同之后就j++,當然我們自然希望下一位繼續相同, 36 但是如果不相同呢?那就要用到我們的p[],假設當前j=5,然后分割出來的兩個區間是完全相同的,同樣的l1!=l2 37 這個時候我們就把j定義當ld,搜索到: lc~l3=l4~ld=la~l5=l6~lb,然后我們的目的就是要讓 38 lc~l3=lb~lb,這個時候只要他們的下一個相同就又有一部分相同的,就是這個道理和上面的是一樣的 39 */ 40 while(j>0 && sa[i]!=sb[j+1]) j=p[j]; 41 if(sa[i]==sb[j+1]) j++; 42 if(j==lenb){ed=i; st=i-lenb+1; break;} 43 } 44 if(j==lenb) printf("%d %d\n",st,ed); else printf("NO\n"); 45 return 0; 46 } Tristan code 注釋版?
(非注釋版,已經能夠理解并且打出代碼的就看這個)
1 #include<cstdio> 2 #include<cstring> 3 #include<cstdlib> 4 #include<algorithm> 5 #include<cmath> 6 #include<iostream> 7 using namespace std; 8 char sa[1000010],sb[1010]; 9 int p[1010]; 10 int main() 11 { 12 scanf("%s",sa+1); scanf("%s",sb+1); 13 int lena=strlen(sa+1),lenb=strlen(sb+1); 14 p[1]=0; 15 for(int i=2;i<=lenb;i++) 16 { 17 int j=p[i-1]; 18 while(j>0 && sb[i]!=sb[j+1]) j=p[j]; 19 if(sb[i]==sb[j+1]) p[i]=j+1; else p[i]=0; 20 } 21 int st,ed,j=0; 22 for(int i=1;i<=lena;i++) 23 { 24 while(j>0 && sa[i]!=sb[j+1]) j=p[j]; 25 if(sa[i]==sb[j+1]) j++; 26 if(j==lenb){ed=i; st=i-lenb+1; break;} 27 } 28 if(j==lenb) printf("%d %d\n",st,ed); else printf("NO\n"); 29 return 0; 30 } Tristan Code 非注釋版?
轉載于:https://www.cnblogs.com/Tristanjiang/p/11356379.html
總結
- 上一篇: cam350删除东西
- 下一篇: 中国计算机网络法规,中国互联网法规汇编.