KMP算法模式匹配
轉載請注明出處
http://blog.csdn.net/pony_maggie/article/details/37832707
作者:小馬
在一個長串中查找一個子串是較常用的操作。各種信息檢索系統,文字處理系統都少不了。本文介紹一個非常著名的KMP模式匹配算法用于子串查找。
?
先拋開KMP,正常情況一下我們會如何設計這個邏輯。一個主串S, 要在里面查找一個子串T,如果找到了返回T在S中開始的位置,否則返回-1。應該不難,需要一個輔助函數,從一個串口的指定位置,取出指定長度的子串。思想是這樣:
在主串S中從開始位置,取長度和T相等的子串比羅,若相等,返回該位置的索引值,否則位置增加1, 繼續上面的過程。代碼很簡單,如下:
?
//普通方法查找子串 //在主串s中查找t, 若找到匹配,返回索引位置(從0到len-1) //否則返回-1 int IndexOfString(char* srcString, char* keyString) {int nSrcLen = 0;int nKeyString = 0;int i = 0;char szTemp[1024] = {0};//假設足夠大if ((srcString == NULL) || (keyString == NULL)){return -1;}nSrcLen = strlen(srcString);nKeyString = strlen(keyString);if (nKeyString > nSrcLen){return -1;}while(i < (nSrcLen-nKeyString)){memset(szTemp, 0x00, sizeof(szTemp));SubString(srcString, szTemp, i, nKeyString);if (memcmp(szTemp, keyString, nKeyString) != 0){i++;}else return i;}return -1; }再進一步,把輔助函數去掉,通過頻繁操作下標也同樣可以實現,思想跟上面查不多,只不過換成一個個字符比較,代碼如下:
?
int IndexOfString1(char* srcString, char* keyString) {int nSrcLen = 0;int nKeyLen = 0;int i = 0;int j = 0;char szTemp[1024] = {0};//假設足夠大if ((srcString == NULL) || (keyString == NULL)){return -1;}nSrcLen = strlen(srcString);nKeyLen = strlen(keyString);if (nKeyLen > nSrcLen){return -1;}while((i < nSrcLen) && (j <nKeyLen)){if (srcString[i] == keyString[j]){i++;j++;}else{i = i - j + 1;//主串回退到下一個位置j = 0; //子串重新開始}}if (j >= nKeyLen){return (i - nKeyLen);//找到}return -1; }分析一下上面算法的時間復雜度(兩個算法其實是一樣的)。舉個例子,主串是:
“A STRING SEARCHING EXAMPLE CONSISTINGOF SIMPLE TEXT”
子串是
“STING”
?
用上面的算法,會發現除了上面標記紅色的字符比較了兩次,其它都是比較一次,如果主串的長度是m, 子串的長度是n, 時間復雜度基本是O(m+n)。好像不錯,效率挺高。再來看一個。主串是:
“000000000000000000000000000000000000000000000000000000000001”
子串是
“00000001”
?
在腦海里想像一下這個過程,很容易得出它的時間復雜度是O(m*n)。所以這種類似窮舉的查找子串算法,時間復雜度與主串和子串的內容有很大關系,復雜度不固定。而且像上面那樣的01串在計算機處理中還比較常見,所以需要更好的算法。
?
KMP(三個發明人的名字首字母)算法可以保證不論哪種情況都可以在O(m+n)的時間復雜度內完成匹配。它改進的地方是當出現比較不等時,主串中位置不回退,而是利用已經匹配的結果,將子串向右滑動盡可能遠的距離后,再繼續比較,看個例子:
?
在第三趟匹配中,當i=12, j=7時,字符不相等,這時不用回退到i=7,j=1重新比較,而是i不變,j變成next[j]的值就行了,上圖中是3, 也就是圖中第四趟的比較位置。
?
這個確實很強大,現在要解決的問題是next[j]如何計算。其實這個推導也不難,很多算法的書上都有詳細的過程,我這里就不贅述了。只把結果給出來:
?
1 當j = 0時,next[j] =-1。
2 當j =1時,next[j] = 0。
3 滿足1 < k < j,且p[0…k-1] =p[j-k…j-1]的最大的k, next[j] = k。
4 其它情況,next[j] = 0。
?
根據上面的邏輯,很容易寫出一個計算next的函數,如下:
static void getNext(char* strSub) {int j, k, temp;int nLen = 0;j = k = temp = 0;nLen = strlen(strSub);memset(g_next, 0x00, sizeof(g_next));//每次進來都清空for (j = 0; j < nLen; j++){if (j == 0){g_next[j] = -1;}else if (j == 1){g_next[j] = 0;}else //取集合不為空時的最大值{temp = j - 1;for (k = temp; k > 0; k--){if (isEqual(strSub, j, k)){g_next[j] = k;break;}}if (k == 0)//集合為空{g_next[j] = 0;}}}}得到next之后,整個過程如下: 以指針i和j分別表示主串和子串中正在比較的字符,若Si = Pj, 則i和j都增1,繼續下一個字符的比較。否則i不變,j退到next[j]的位置再比較,若相等,再各自增1,否則j再退到next[j]。循環進行,如果中間遇到next[j]為-1的情況,此時主串要增1,子串j變為0,表示要從主串的下一個位置重新開始比較。
?
把上面的過程轉化為代碼:
//KMP算法查找子串 //在主串s中查找t, 若找到匹配,返回索引位置(從0到len-1) //否則返回-1 int IndexOfStringKMP(char* srcString, char* keyString) {int i = 0; int j = 0;int nSrcLen = 0;int nKeyLen = 0;if ((srcString == NULL) || (keyString == NULL)){return -1;}nSrcLen = strlen(srcString);nKeyLen = strlen(keyString);if (nKeyLen > nSrcLen){return -1;}getNext(keyString);//先計算next值while ((i < nSrcLen) && (j < nKeyLen)){if ((srcString[i] == keyString[j]) || (j == -1)){i++;j++;}else{j = g_next[j];}}if (j >= nKeyLen){return (i - nKeyLen);//找到}return -1; }代碼下載地址:
http://download.csdn.net/detail/pony_maggie/7630329
或
https://github.com/pony-maggie/StringIndex
總結
- 上一篇: php仪表盘图,仪表盘图文模式
- 下一篇: 江苏科技大学计算机科学与技术学院官网,郭