KMP算法模式匹配

最后更新于:2022-04-01 15:58:21

转载请注明出处 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)的时间复杂度内完成匹配。它改进的地方是当出现比较不等时,主串中位置不回退,而是利用已经匹配的结果,将子串向右滑动尽可能远的距离后,再继续比较,看个例子: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-13_575e9295265c7.jpg) 在第三趟匹配中,当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
';