zoukankan      html  css  js  c++  java
  • KMP算法模式匹配

    转载请注明出处

    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

  • 相关阅读:
    文件操作方法大全以及文件打开的其他一些模式sys.stdout.write()就是标准输出到你当前的屏幕 sys.stdout.flush()把内存立即显示到您当前的屏幕
    文件操作之循环
    python中的enumerate函数用于遍历序列中的元素以及它们的下标
    文件操作open,r,w,a三种模式
    盖得化工--采集所有公司详细信息
    盖得化工--采集二级网址的公司详细信息
    [python网络编程]使用scapy修改源IP发送请求
    盖得化工----requests/bs4---采集二级网址
    盖得化工--selenium翻页测试
    盖得化工网——翻页测试---页码框输入页码
  • 原文地址:https://www.cnblogs.com/wzjhoutai/p/7210143.html
Copyright © 2011-2022 走看看