KMP字符串模式匹配详解
生活随笔
收集整理的這篇文章主要介紹了
KMP字符串模式匹配详解
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
?KMP字符串模式匹配詳解
KMP字符串模式匹配通俗點說就是一種在一個字符串中定位另一個串的高效算法。簡單匹配算法的時間復雜度為O(m*n);KMP匹配算法。可以證明它的時間復雜度為O(m+n).。 一.簡單匹配算法 先來看一個簡單匹配算法的函數: int Index_BF ( char S [ ], char T [ ], int pos ) { /* 若串 S 中從第pos(S 的下標0≤pos<StrLength(S))個字符 起存在和串 T 相同的子串,則稱匹配成功,返回第一個 這樣的子串在串 S 中的下標,否則返回 -1 */ int i = pos, j = 0; while ( S[i+j] != '/0'&& T[j] != '/0') if ( S[i+j] == T[j] ) j ++; // 繼續比較后一字符 else { i ++; j = 0; // 重新開始新的一輪匹配 } if ( T[j] == '/0') return i; // 匹配成功 返回下標 else return -1; // 串S中(第pos個字符起)不存在和串T相同的子串 } // Index_BF 此算法的思想是直截了當的:將主串S中某個位置i起始的子串和模式串T相比較。即從?j=0?起比較S[i+j]?與?T[j],若相等,則在主串?S?中存在以?i?為起始位置匹配成功的可能性,繼續往后比較( j逐步增1 ),直至與T串中最后一個字符相等為止,否則改從S串的下一個字符起重新開始進行下一輪的"匹配",即將串T向后滑動一位,即?i?增1,而?j?退回至0,重新開始新一輪的匹配。 例如:在串S=”abcabcabdabba”中查找T=” abcabd”(我們可以假設從下標0開始):先是比較S[0]和T[0]是否相等,然后比較S[1]?和T[1]是否相等…我們發現一直比較到S[5]?和T[5]才不等。如圖: 當這樣一個失配發生時,T下標必須回溯到開始,S下標回溯的長度與T相同,然后S下標增1,然后再次比較。如圖: 這次立刻發生了失配,T下標又回溯到開始,S下標增1,然后再次比較。如圖: 這次立刻發生了失配,T下標又回溯到開始,S下標增1,然后再次比較。如圖:又一次發生了失配,所以T下標又回溯到開始,S下標增1,然后再次比較。這次T中的所有字符都和S中相應的字符匹配了。函數返回T在S中的起始下標3。如圖:
二. KMP匹配算法 還是相同的例子,在S=”abcabcabdabba”中查找T=”abcabd”,如果使用KMP匹配算法,當第一次搜索到S[5]?和T[5]不等后,S下標不是回溯到1,T下標也不是回溯到開始,而是根據T中T[5]==’d’的模式函數值(next[5]=2,為什么?后面講),直接比較S[5]?和T[2]是否相等,因為相等,S和T的下標同時增加;因為又相等,S和T的下標又同時增加。。。最終在S中找到了T。如圖:
KMP匹配算法和簡單匹配算法效率比較,一個極端的例子是: 在S=“AAAAAA…AAB“(100個A)中查找T=”AAAAAAAAAB”,?簡單匹配算法每次都是比較到T的結尾,發現字符不同,然后T的下標回溯到開始,S的下標也要回溯相同長度后增1,繼續比較。如果使用KMP匹配算法,就不必回溯. 對于一般文稿中串的匹配,簡單匹配算法的時間復雜度可降為O (m+n),因此在多數的實際應用場合下被應用。 KMP算法的核心思想是利用已經得到的部分匹配信息來進行后面的匹配過程。看前面的例子。為什么T[5]==’d’的模式函數值等于2(next[5]=2),其實這個2表示T[5]==’d’的前面有2個字符和開始的兩個字符相同,且T[5]==’d’不等于開始的兩個字符之后的第三個字符(T[2]=’c’).如圖: 也就是說,如果開始的兩個字符之后的第三個字符也為’d’,那么,盡管T[5]==’d’的前面有2個字符和開始的兩個字符相同,T[5]==’d’的模式函數值也不為2,而是為0。 ???前面我說:在S=”abcabcabdabba”中查找T=”abcabd”,如果使用KMP匹配算法,當第一次搜索到S[5]?和T[5]不等后,S下標不是回溯到1,T下標也不是回溯到開始,而是根據T中T[5]==’d’的模式函數值,直接比較S[5]?和T[2]是否相等。。。為什么可以這樣? 剛才我又說:“(next[5]=2),其實這個2表示T[5]==’d’的前面有2個字符和開始的兩個字符相同”。請看圖?:因為,S[4] ==T[4],S[3] ==T[3],根據next[5]=2,有T[3]==T[0],T[4] ==T[1],所以S[3]==T[0],S[4] ==T[1](兩對相當于間接比較過了),因此,接下來比較S[5]?和T[2]是否相等。。。 有人可能會問:S[3]和T[0],S[4]?和T[1]是根據next[5]=2間接比較相等,那S[1]和T[0],S[2]?和T[0]之間又是怎么跳過,可以不比較呢?因為S[0]=T[0],S[1]=T[1],S[2]=T[2],而T[0]?!=?T[1], T[1]?!=?T[2],==>?S[0]?!= S[1],S[1] != S[2],所以S[1]?!= T[0],S[2] != T[0].?還是從理論上間接比較了。 有人疑問又來了,你分析的是不是特殊輕況啊。 假設S不變,在S中搜索T=“abaabd”呢?答:這種情況,當比較到S[2]和T[2]時,發現不等,就去看next[2]的值,next[2]=-1,意思是S[2]已經和T[0]?間接比較過了,不相等,接下來去比較S[3]和T[0]吧。 假設S不變,在S中搜索T=“abbabd”呢?答:這種情況當比較到S[2]和T[2]時,發現不等,就去看next[2]的值,next[2]=0,意思是S[2]已經和T[2]比較過了,不相等,接下來去比較S[2]和T[0]吧。 假設S=”abaabcabdabba”在S中搜索T=“abaabd”呢?答:這種情況當比較到S[5]和T[5]時,發現不等,就去看next[5]的值,next[5]=2,意思是前面的比較過了,其中,S[5]的前面有兩個字符和T的開始兩個相等,接下來去比較S[5]和T[2]吧。 總之,有了串的next值,一切搞定。那么,怎么求串的模式函數值next[n]呢?(本文中next值、模式函數值、模式值是一個意思。) 三.?怎么求串的模式值next[n] 定義: (1)next[0]= -1?意義:任何串的第一個字符的模式值規定為-1。 (2)next[j]= -1???意義:模式串T中下標為j的字符,如果與首字符 相同,且j的前面的1—k個字符與開頭的1—k 個字符不等(或者相等但T[k]==T[j])(1≤k<j)。 如:T=”abCabCad”?則?next[6]=-1,因T[3]=T[6] (3)next[j]=k????意義:模式串T中下標為j的字符,如果j的前面k個 字符與開頭的k個字符相等,且T[j] != T[k]?(1≤k<j)。 ???????????????????????即T[0]T[1]T[2]。。。T[k-1]== T[j-k]T[j-k+1]T[j-k+2]…T[j-1] 且T[j] != T[k].(1≤k<j); (4) next[j]=0???意義:除(1)(2)(3)的其他情況。 舉例: 01)求T=“abcac”的模式函數的值。 ???? next[0]= -1?根據(1) ???? next[1]=0???根據?(4)???因(3)有1<=k<j;不能說,j=1,T[j-1]==T[0] ???? next[2]=0???根據?(4)???因(3)有1<=k<j;(T[0]=a)!=(T[1]=b) ???? next[3]= -1?根據?(2) ???? next[4]=1???根據?(3)?T[0]=T[3]?且?T[1]=T[4] ????即???
| 下標 | 0 | 1 | 2 | 3 | 4 |
| T | a | b | c | a | c |
| next | -1 | 0 | 0 | -1 | 1 |
| 下標 | 0 | 1 | 2 | 3 | 4 |
| T | a | b | c | a | b |
| next | -1 | 0 | 0 | -1 | 0 |
| 下標 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| T | a | b | a | b | c | a | a | b | c |
| next | -1 | 0 | -1 | 0 | 2 | -1 | 1 | 0 | 2 |
| 下標 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| T | a | b | C | a | b | C | a | d |
| next | -1 | 0 | 0 | -1 | 0 | 0 | -1 | 4 |
| 下標 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| T | a | d | C | a | d | C | a | d |
| next | -1 | 0 | 0 | -1 | 0 | 0 | -1 | 0 |
void get_nextval(const char *T, int next[]) {// 求模式串T的next函數值并存入數組 next。int j = 0, k = -1;next[0] = -1;while ( T[j/*+1*/] != '/0' ){if (k == -1 || T[j] == T[k]){++j; ++k;if (T[j]!=T[k])next[j] = k;elsenext[j] = next[k];}// ifelsek = next[k];}// while這里是我加的顯示部分// for(int i=0;i<j;i++)//{// cout<<next[i];//}//cout<<endl; }// get_nextval 另一種寫法,也差不多。 void getNext(const char* pattern,int next[]) {next[0]= -1;int k=-1,j=0;while(pattern[j] != '/0'){if(k!= -1 && pattern[k]!= pattern[j] )k=next[k];++j;++k;if(pattern[k]== pattern[j])next[j]=next[k];elsenext[j]=k;}這里是我加的顯示部分// for(int i=0;i<j;i++)//{// cout<<next[i];//}//cout<<endl; } 下面是KMP模式匹配程序,各位可以用他驗證。記得加入上面的函數 #include <iostream.h> #include <string.h> int KMP(const char *Text,const char* Pattern) //const 表示函數內部不會改變這個參數的值。 {if( !Text||!Pattern|| Pattern[0]=='/0' || Text[0]=='/0' )//return -1;//空指針或空串,返回-1。int len=0;const char * c=Pattern;while(*c++!='/0')//移動指針比移動下標快。{ ++len;//字符串長度。}int *next=new int[len+1];get_nextval(Pattern,next);//求Pattern的next函數值int index=0,i=0,j=0;while(Text[i]!='/0' && Pattern[j]!='/0' ){if(Text[i]== Pattern[j]){++i;// 繼續比較后繼字符++j;}else{index += j-next[j];if(next[j]!=-1)j=next[j];// 模式串向右移動else{j=0;++i;}}}//whiledelete []next;if(Pattern[j]=='/0')return index;// 匹配成功elsereturn -1; } int main()//abCabCad {char* text="bababCabCadcaabcaababcbaaaabaaacababcaabc";char*pattern="adCadCad";//getNext(pattern,n);//get_nextval(pattern,n);cout<<KMP(text,pattern)<<endl;return 0; }
五.其他表示模式值的方法 上面那種串的模式值表示方法是最優秀的表示方法,從串的模式值我們可以得到很多信息,以下稱為第一種表示方法。第二種表示方法,雖然也定義next[0]= -1,但后面絕不會出現-1,除了next[0],其他模式值next[j]=k(0≤k<j)的意義可以簡單看成是:下標為j的字符的前面最多k個字符與開始的k個字符相同,這里并不要求T[j] != T[k]。其實next[0]也可以定義為0(后面給出的求串的模式值的函數和串的模式匹配的函數,是next[0]=0的),這樣,next[j]=k(0≤k<j)的意義都可以簡單看成是:下標為j的字符的前面最多k個字符與開始的k個字符相同。第三種表示方法是第一種表示方法的變形,即按第一種方法得到的模式值,每個值分別加1,就得到第三種表示方法。第三種表示方法,我是從論壇上看到的,沒看到詳細解釋,我估計是為那些這樣的編程語言準備的:數組的下標從1開始而不是0。 下面給出幾種方法的例子: ??????表一。
| 下標 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| T | a | b | a | b | c | a | a | b | c |
| (1) next | -1 | 0 | -1 | 0 | 2 | -1 | 1 | 0 | 2 |
| (2) next | -1 | 0 | 0 | 1 | 2 | 0 | 1 | 1 | 2 |
| (3) next | 0 | 1 | 0 | 1 | 3 | 0 | 2 | 1 | 3 |
| 下標 | 0 | 1 | 2 | 3 | 4 |
| T | a | b | c | A | c |
| (1)next | -1 | 0 | 0 | -1 | 1 |
| (2)next | -1 | 0 | 0 | 0 | 1 |
| 下標 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| T | a | d | C | a | d | C | a | d |
| (1)next | -1 | 0 | 0 | -1 | 0 | 0 | -1 | 0 |
| (2)next | -1 | 0 | 0 | 0 | 1 | 2 | 3 | 4 |
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀
總結
以上是生活随笔為你收集整理的KMP字符串模式匹配详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 图像的特征提取算法
- 下一篇: C++ STL算法之accumulate