转载请注明出处
http://blog.csdn.net/pony_maggie/article/details/37832707
作者:小马
在一个长串中查找一个子串是较经常使用的操作。各种信息检索系统,文字处理系统都少不了。本文介绍一个很著名的KMP模式匹配算法用于子串查找。
先抛开KMP。正常情况一下我们会怎样设计这个逻辑。一个主串S, 要在里面查找一个子串T,假设找到了返回T在S中開始的位置,否则返回-1。应该不难。须要一个辅助函数,从一个串口的指定位置。取出指定长度的子串。思想是这样:
在主串S中从開始位置。取长度和T相等的子串比罗,若相等,返回该位置的索引值。否则位置添加1, 继续上面的过程。
代码非常easy。例如以下:
//普通方法查找子串 //在主串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”
在脑海里想像一下这个过程,非常easy得出它的时间复杂度是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。
依据上面的逻辑。非常easy写出一个计算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