KMP笔记

KMP算法,字符串的模式匹配。

简单的匹配也是可以的,而且很容易就想到,只是在时间上浪费了很多不必要的比较。

那么在真正匹配之前对目标子串做一个处理,将会在一定程度上省去很多不必要的比较,大大提高效率。

比如,target:ababaaaba  这样一个字符串在str字符串中去寻找匹配。那么首先对当前target字符串做一个处理,所谓处理就是记录一下那些字符是与前面重复了,再匹配过程中已经想等就不用再重复比较,通常为target处理得到一个next数组,这个数字就是记录到当前有多少个字符与重复相等的。具体:

next【i】:

0,  i=0或者没有前缀后缀字符相等;

max{k|0<=k<=j,p0...pk=pj-k...pj}集合非空,即有前后缀相等;

 

例子:ababaaaba

next: next【0】=0;

    next【1】=0(a!=b);

    next【2】=1(aba中a==a);

    next【3】=2(abab中ab=ab);

    next【4】=3(ababa中aba=aba);

    next【5】=1(ababaa中abab != abaa, j回溯往前为next[2]=1, a[1]!=a[5], 则j继续回溯为next[0]=0,a[0]==a[5]);

    next【6】=1(j=1,但a[1]!=a[6],所以j回溯为a[0]=0,a[0]==a[6]);

    next【7】=2(a[1]==a[7]);

    next【8】=3(a[2]==a[8]);

获取next数组的函数:

vector<int> getNext(const string &needle)
{
    vector<int> next(needle.size());
    if (needle.size() == 0)
        return next;
    next[0] = 0;
    int i = 1, j = 0;
    while (i<needle.size())
    {
        if ( needle[i] == needle[j])  //前面最后一个字符等于后面最后一个字符,所以next数组当前标记为j+1
            next[i++] = ++j;
        else if (j != 0)
            j = next[j - 1];//不等的时候,j往前回溯一个,跳到上一个比较下来相等的位置下标
        else
            next[i++] = 0;  //前后的后缀字符不等,且此时j为0,即与第一个字符比较既然不等,next数据当然将其标记为0
    }
    return next;
}

 

得到next数组之后,在匹配中用两个指针,i表示str中下标位置,j表示target中下标位置。每次逐一比较,如果相等依次往后推进,如果不等,则让j回溯到上一next【j】,即中间重复的就不再比较辣,下次开始从回溯的位置开始比较。

int find(const string &haystack, const string &needle, int pos)
{
    if (haystack.size()<needle.size() || pos<0 || pos>haystack.size() - 1)
        return -1;
    vector<int> next = getNext(needle);
    int i = pos, j = 0;
    while (i<haystack.size() && j<needle.size())
    {

        if (haystack[i] == needle[j])   //如果两个字符串的当前字符相等,则一次往后推一个就好
        {
            ++i;
            ++j;
        }
        if (j == needle.size())    //如果,目标字符已经达到最末尾,则可以返回超找到的位置了。
            return i - j;
        if (i<haystack.size() && haystack[i] != needle[j]) //如果当前比较的字符不相等,则按照next数组中往上回溯一个下标,再次比较即可
        {
            if (j != 0)//简单的j按照next数组回溯
                j = next[j - 1];
            else    //目标字符串的第一个字符就没有匹配上,则让长的字符数组往后推进一个再进行比较
                ++i;
        }
    }
    return -1;
}

 

以上代码为从str中的pos位置开始往后查找匹配。

 

 

next 数组改进:每次对于是与第一个字符比较还是中间的字符比较区分开来,即记录上中间已经相等的字符,匹配的时候可以直接略过。

如:aaaabcdef中匹配aaaaax

现在当匹配到第四个d与a不等时,不用再去比较b和a前面的a,直接跳到第一个a,因为中间的a是相等的,在nextval数组中已经记录略过了

vector<int> GetNextVal(const string &str)
{
	vector<int> nextVal(str.size());
	if (str.size() == 0)
		return nextVal;
	int i = 0,j = 0;
	while (i<str.size()-1)
	{
		if (j == 0)
			nextVal[++i] = j++;
		else if (str[i] == str[j - 1])
			nextVal[++i] = nextVal[j++];
		else
			j = nextVal[j - 1];
	}
	return nextVal;

}

 

posted @ 2017-04-24 20:11  糯米米一粒  阅读(137)  评论(0编辑  收藏  举报