題目:
7-1 串的模式匹配 (30 分)
給定一個主串S(長度<=10^6)和一個模式T(長度<=10^5),要求在主串S中找出與模式T相匹配的子串,返回相匹配的子串中的第一個字符在主串S中出現的位置。
輸入格式:
輸入有兩行: 第一行是主串S; 第二行是模式T.
輸出格式:
輸出相匹配的子串中的第一個字符在主串S中出現的位置。若匹配失敗,輸出0.
輸入樣例:
在這里給出一組輸入。例如:
aaaaaba
ba
輸出樣例:
在這里給出相應的輸出。例如:
6
分析:
這里就是在主串里面找是否存在和模式串相等的子串啦,
如果存在就輸出該子串在主串中第一個字符的位置,否則輸出0。
主要有兩種方法:
1.BF算法(在數據量大的時候可能會導致運行超時)
2.KMP算法
這里將采用KMP算法
代碼:
#include<iostream> #include<string.h> using namespace std; /*在本題中有如例題般屈指可數的字符串,也有10^6如此龐大的數據, 為了避免不必要的空間浪費,筆者決定在輸入字符串string s后 化為字符數組 char *s[s.length()] 為此,我還特地查找了string的最大容量: 堆開得足夠大,數組的最大長度是可以不斷增大的(推測最長的長度為 2^32,也就是4G。) 但編碼時有需要注意的地方,采用明文的方式,如果超過65534個字節,可能報編譯錯誤。 */ char* trans(string str) { int size = str.length(); char *s; s= new char[size]; strcpy(s, str.c_str()); return s; } void calc_next(string pstring, int *next) { next[0] = -1;//-1表示模式串開頭 int j = 0, k = -1; int p_len = pstring.length();//模式串長度 char *p = trans(pstring);//模式串字符數組 while(j<p_len){//當j尚未指向模式串尾端時 if(k == -1 || p[j] == p[k]) { k++;//k指前綴開始下標 j++;//j指后綴開始下標 next[j] = k;//next[]存放已匹配子串中最長前后綴長度 //其中,next[j]表示 p[0]-p[j-1]子串中最長前后綴長度 }else{ k = next[k]; //k回溯到模式串開頭 } } } int kmp(string sstring, string pstring) { int *next = new int[pstring.length()]; calc_next(pstring, next);//得到 next[]數組 char *s = trans(sstring), *p = trans(pstring); //轉字符串為字符數組 int i=0, j=0; int pos = 0; while( i<=sstring.length() || j<=pstring.length()){ if( j == -1 || s[i] == p[j]){ i++; j++; }else{ j = next[j]; /* ①有最長前后綴時: 當主串和模式串在主串s[j]位置(即模式串最長后綴后一位) 不匹配時, s[j]將和 模式串最長前綴后一位比較 ②冇最長前后綴時: j = next[0] == -1; 整個模式串向后移一位 */ } if(j == pstring.length()){//匹配成功 pos = i-j+1; break; } } return pos; } int main() { string sstring, pstring;//定義字符串 getline(cin, sstring);//輸入字符串 getline(cin, pstring); cout<<kmp(sstring, pstring); return 0; }
2019.04.07 22:08更新
繼續查閱資料的時候發現了一個可以優化原始KMP算法的判斷條件:
以上圖為例
我們的模式串 p[j] 是與主串中 s[i] 不匹配時才開始第一次移位,但是我們發現有一種情況:
模式串中的 p[k] == p[j] ,p[j] != s[i] ;由此我們可以知道 p[k] != s[i] ,那么這時我們需要進行第二次移位。
針對上述情況,不妨在第一次移位前增加一個判斷條件,即當 p[j] == p[k] 時令 next[j] = next[k] ,如此便可一步到位,優化算法。
部分代碼更改如下:
while(j<p_len){//當j尚未指向模式串尾端時 if(k == -1 || p[j] == p[k]) { k++;//k指前綴開始下標 j++;//j指后綴開始下標 if(p[j] == p[k]){ next[j] = next[k]; }else{ next[j] = k;//next[]存放已匹配子串中最長前后綴長度 //其中,next[j]表示 p[0]-p[j-1]子串中最長前后綴長度 } }else{ k = next[k]; //k回溯到模式串開頭 } }
編程中遇到的困難:
1.KMP算法相對於BF算法或者時其他算法來說更為抽象,沒有辦法很好地從利用該算法的目的、優勢、所需操作方法等內容正向理解。
需要從KMP核心原理入手才能夠感悟到數據結構之美。
2.值得注意的一點是(部分博客沒有說清楚)我們所求的最長前后綴是在已經匹配的模式串子串中,而非整個模式串,更非主串。
總結:
在實際的應用上,BF算法相對於KMP算法來說使用可能更為方便、簡潔,實操性更強。但是KMP算法的意義在於,這是人類第一次發現
串的模式匹配問題能夠以線性模式來簡化。在KMP的學習上,與其說是學會了一種數據結構,不妨說是與自己來了一場頭腦風暴。當然,
生命不息,進步不止,在查閱KMP資料的時候,后人對於一開始的版本也進行了更多的優化,不斷完善。
時間復雜度 空間復雜度
BF算法 O(n*m) O(1)
KMP算法 O(n+m) O(m)
注:n為主串長度,m為模式串長度。KMP算法犧牲了一定的空間換取時間上的簡化。
解析:KMP算法的時間復雜度由匹配的時間復雜度O(n)+求next數組的時間復雜度O(m)求得
空間復雜度為輔助數組next[m]求得
參考資料:
1.百度百科 https://baike.baidu.com/item/kmp%E7%AE%97%E6%B3%95/10951804?fr=aladdin
2.https://blog.csdn.net/x__1998/article/details/79951598
3.《數據結構(c語言版)》李雲清等編著
以上僅為自己的一些拙見,如有不正確的地方歡迎指出。