数据结构(十三)串 KMP算法模式匹配

来源:互联网 发布:陶哲轩现状 知乎 编辑:程序博客网 时间:2024/05/19 11:44

数据结构串

串由零个或多个字符组成的有限序列

 

串的模式匹配——子串的定位操作

1.      朴素模式匹配  (低效)

对主串的每一个字符作为子串开头,与要匹配的字符串进行匹配。对主串做大循环,每个字符开头做T的长度的小循环,知道匹配成功或全部遍历为止。

2.      KMP模式匹配算法

核心思想:利用已经得到的部分匹配信息来进行后面的匹配过程。

以下是转载的有关KMP模式匹配算法的知识,我还是没太看懂。。。一起学习学习。。



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

 

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

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

 

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. //普通方法查找子串  
  2. //在主串s中查找t, 若找到匹配,返回索引位置(从0到len-1)  
  3. //否则返回-1  
  4. int IndexOfString(char* srcString, char* keyString)  
  5. {  
  6.     int nSrcLen = 0;  
  7.     int nKeyString = 0;  
  8.     int i = 0;  
  9.     char szTemp[1024] = {0};//假设足够大  
  10.   
  11.     if ((srcString == NULL) || (keyString == NULL))  
  12.     {  
  13.         return -1;  
  14.     }  
  15.   
  16.     nSrcLen = strlen(srcString);  
  17.     nKeyString = strlen(keyString);  
  18.     if (nKeyString > nSrcLen)  
  19.     {  
  20.         return -1;  
  21.     }  
  22.   
  23.     while(i < (nSrcLen-nKeyString))  
  24.     {  
  25.         memset(szTemp, 0x00, sizeof(szTemp));  
  26.         SubString(srcString, szTemp, i, nKeyString);  
  27.         if (memcmp(szTemp, keyString, nKeyString) != 0)  
  28.         {  
  29.             i++;  
  30.         }  
  31.         else return i;  
  32.     }  
  33.   
  34.     return -1;  
  35. }  

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

 

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. int IndexOfString1(char* srcString, char* keyString)  
  2. {  
  3.     int nSrcLen = 0;  
  4.     int nKeyLen = 0;  
  5.     int i = 0;  
  6.     int j = 0;  
  7.     char szTemp[1024] = {0};//假设足够大  
  8.   
  9.     if ((srcString == NULL) || (keyString == NULL))  
  10.     {  
  11.         return -1;  
  12.     }  
  13.   
  14.     nSrcLen = strlen(srcString);  
  15.     nKeyLen = strlen(keyString);  
  16.     if (nKeyLen > nSrcLen)  
  17.     {  
  18.         return -1;  
  19.     }  
  20.   
  21.     while((i < nSrcLen) && (j <nKeyLen))  
  22.     {  
  23.         if (srcString[i] == keyString[j])  
  24.         {  
  25.             i++;  
  26.             j++;  
  27.         }  
  28.         else  
  29.         {  
  30.             i = i - j + 1;//主串回退到下一个位置  
  31.             j = 0; //子串重新开始  
  32.         }  
  33.     }  
  34.   
  35.     if (j >= nKeyLen)  
  36.     {  
  37.         return (i - nKeyLen);//找到  
  38.     }  
  39.   
  40.     return -1;  
  41. }  


分析一下上面算法的时间复杂度(两个算法其实是一样的)。举个例子,主串是:

“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 plain copy
 在CODE上查看代码片派生到我的代码片
  1. static void getNext(char* strSub)  
  2. {  
  3.     int j, k, temp;  
  4.     int nLen = 0;  
  5.     j = k = temp = 0;  
  6.     nLen = strlen(strSub);  
  7.   
  8.     memset(g_next, 0x00, sizeof(g_next));//每次进来都清空  
  9.   
  10.     for (j = 0; j < nLen; j++)  
  11.     {  
  12.         if (j == 0)  
  13.         {  
  14.             g_next[j] = -1;  
  15.         }  
  16.         else if (j == 1)  
  17.         {  
  18.             g_next[j] = 0;  
  19.         }  
  20.         else //取集合不为空时的最大值  
  21.         {  
  22.             temp = j - 1;  
  23.             for (k = temp; k > 0; k--)  
  24.             {  
  25.                 if (isEqual(strSub, j, k))  
  26.                 {  
  27.                     g_next[j] = k;  
  28.                     break;  
  29.                 }  
  30.             }  
  31.             if (k == 0)//集合为空  
  32.             {  
  33.                 g_next[j] = 0;  
  34.             }  
  35.         }  
  36.     }  
  37.       
  38.   
  39. }  

得到next之后,整个过程如下: 以指针i和j分别表示主串和子串中正在比较的字符,若Si = Pj, 则i和j都增1,继续下一个字符的比较。否则i不变,j退到next[j]的位置再比较,若相等,再各自增1,否则j再退到next[j]。循环进行,如果中间遇到next[j]为-1的情况,此时主串要增1,子串j变为0,表示要从主串的下一个位置重新开始比较。

 

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

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. //KMP算法查找子串  
  2. //在主串s中查找t, 若找到匹配,返回索引位置(从0到len-1)  
  3. //否则返回-1  
  4. int IndexOfStringKMP(char* srcString, char* keyString)  
  5. {  
  6.     int i = 0;   
  7.     int j = 0;  
  8.     int nSrcLen = 0;  
  9.     int nKeyLen = 0;  
  10.   
  11.     if ((srcString == NULL) || (keyString == NULL))  
  12.     {  
  13.         return -1;  
  14.     }  
  15.   
  16.     nSrcLen = strlen(srcString);  
  17.     nKeyLen = strlen(keyString);  
  18.     if (nKeyLen > nSrcLen)  
  19.     {  
  20.         return -1;  
  21.     }  
  22.   
  23.     getNext(keyString);//先计算next值  
  24.   
  25.     while ((i < nSrcLen) && (j < nKeyLen))  
  26.     {  
  27.         if ((srcString[i] == keyString[j]) || (j == -1))  
  28.         {  
  29.             i++;  
  30.             j++;  
  31.         }  
  32.         else  
  33.         {  
  34.             j = g_next[j];  
  35.         }  
  36.     }  
  37.     if (j >= nKeyLen)  
  38.     {  
  39.         return (i - nKeyLen);//找到  
  40.     }  
  41.   
  42.     return -1;  
  43. }  
0 0
原创粉丝点击