您的位置:首页 > 其它

KMP算法模式匹配

2014-08-14 14:19 162 查看
转载请注明出处
http://blog.csdn.net/pony_maggie/article/details/37832707
作者:小马

在一个长串中查找一个子串是较常用的操作。各种信息检索系统,文字处理系统都少不了。本文介绍一个非常著名的KMP模式匹配算法用于子串查找。

先抛开KMP,正常情况一下我们会如何设计这个逻辑。一个主串S, 要在里面查找一个子串T,如果找到了返回T在S中开始的位置,否则返回-1。应该不难,需要一个辅助函数,从一个串口的指定位置,取出指定长度的子串。思想是这样:

在主串S中从开始位置,取长度和T相等的子串比罗,若相等,返回该位置的索引值,否则位置增加1, 继续上面的过程。代码很简单,如下:

[cpp] view
plaincopy





//普通方法查找子串

//在主串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;

}

再进一步,把辅助函数去掉,通过频繁操作下标也同样可以实现,思想跟上面查不多,只不过换成一个个字符比较,代码如下:

[cpp] view
plaincopy





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的函数,如下:

[cpp] view
plaincopy





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,表示要从主串的下一个位置重新开始比较。

把上面的过程转化为代码:

[cpp] view
plaincopy





//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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: