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