KMP算法


注:这篇博客不是纯讲解类博客,只是个人学习记录。
智商下线越来越严重了,一个算法快看了一天,这还没算上课堂上看的时间,我太复杂了。

KMP算法是用来解决字符串的匹配问题的。

假定现在有两个字符串,一个文本串text,一个模式串patten。现在要求我们判断模式串pattern是否是text的字串,该怎么做呢?

pattern要想是text的子串,必须在text中找到一段与pattern完全相同的部分字符,于是很容易想到接下来的工作大致会涉及到以下两步:

  1. 遍历。text与pattern都需要遍历。
  2. 比较。不比较怎么可能知道字符间是否相等?

比较很简单,if语句加 == 运算符就可以,至于遍历,因为涉及到两条字符串我们需要引入两个哨兵变量i,j,初始时i,j分别指向pattern与text的头部,之后不断令i++,j++就行了。

最容易想到的并且最容易理解的是算法执行效率比较差的暴力算法:

bool StrMatch(chat *text,char *pattern)
{
    int i = 0,j = 0;
    int n = strlen(text);
    int m = strlen(pattern);
    while(i < n && j < m)
    {
        if(text[i] == pattern[j])
        {
            i++;
            j++;
        }
        else
        {
            i = i - j + 1; 
            j = 0;
        }
    }
    if(j == (m - 1))
        return true;
    return false;
}

在暴力匹配算法中,一旦出现匹配失败,i的值会回到最初开始匹配的位置的后一位,然后重新开始匹配。我们把这种操作称作i的回溯,正是由于i的不断回溯,这种算法的复杂度在最坏情况下达到了O(n*m),效率显然不能使人满意。

KMP算法的精髓就在于去掉了i的回溯,使得效率达到了O(n + m)。

举两个例子:

1.s[i] != p[j+1]

text = "abacbad"
pattern = "abad"
第四位出现失配,此时i = 3,j = 2.注意如果匹配成功则i,j都为3,但因为失配所以
j不能加1,j仍为失配位的前一位即2.
令 j = next[j] = 0,之后比较text[3] 与 pattern[1]......

2.j == -1

text = "abcxyz";
pattern = "abcd"
第四位出现失配,此时i=3,j=2,i原本应该不动等待j的跳跃,但由于模式串abcd中
无相同前后缀,故只好令i前进1位,重新从头匹配。

C代码如下:

const int maxn = 100;
int next[maxn];
void GetNext(char *p,int len)
{
	int j = -1;
	next[0] = -1;
	for(int i = 1; i < len; i++)
	{
		while( j != -1 && p[i] != p[j+1])
			j = next[j];
		if(p[i] == p[j+1])
			++j;
		next[i] = j;
	}
}
int KMP(char *s,char *p)
{
	int j = -1;
	int n = strlen(s);
	int m = strlen(p);
	GetNext(p,m);
	for(int i = 0; i < n; i++)
	{
		while(j != -1 && s[i] != p[j+1]) //注意这里为什么要要求j不等于-1呢?因为j等于-1就不需要移动j了,j等于-1说明模式串需要重新从头匹配,
                  j = next[j];               //它刚好对应于p[j+1]=p[-1+1]=p[0],因此注意j==-1时只需要i++即可。	
		if(s[i] == p[j+1])
			j++;
		if(j == m-1)
			return i - j;
	}
	return -1;
}

Python代码:

def GetNext(p):
    j,p_len = -1,len(p)
    nextv = [-1] * p_len
    for i in range(1,p_len):
        while j != -1 and p[i] != p[j+1]:
            j = nextv[j]
        if p[i] == p[j+1]:
            j += 1
        nextv[i] = j
    return nextv
def KMP(s,p):
    j,nextv = -1,GetNext(p)
    for i in range(len(s)):
        while j != -1 and s[i] != p[j+1]:
            j = nextv[j]
        if s[i] == p[j+1]:
            j += 1
        if j == len(p) - 1:
            return i - j
    return -1

nextval数组

若:
pattern = "ababab",text = "ababacab"
则:
next = {-1,-1,0,1,2,3}
当i = 5,j = 4,'c' != 'b',失配;
j = next[j] = 2,i = 5. 'c' != 'b',失配;
j = next[j] = 0,i = 5. 'c' != 'b',失配;
j = next[j] = -1. j == -1,i++;

从这个例子中我们可能已经发现,对于一个模式串来说,它可能存在以下等式:

pattern[j+1] == pattern[next[j]+1] == pattern[next[next[j]]+1] == ......

这样的话,如果第j+1位失配,那么它的第next[j]+1位肯定也会失配,它的next[next[j]]+1位肯定也会失配,既然如此,何不把这些多余的工作全部省去呢?直接令j一步到底,这样就得到了next数组的优化——nextval数组。

要想让程序跳过无意义的回退,需满足pattern[i+1] != pattern[next[i]+1],而在普通的GetNext函数中,在令next[i] = j之前,j已经指向原先的next[i],因此原先的满足条件即变为pattern[i+1] != pattern[j+1]

代码如下:

void GetNextval(char *p,int len)
{
    int j = -1;
    nextval[0] = -1;
    for(int i = 1; i < len; i++)
    {
        if(j != -1 && p[i] != p[j+1]) //这里因为j = nextval[j]最多只会进行一次,因此可用if代替while
            j = nextval[j];
        if(p[i] == p[j+1])
            j++;
        if(j == -1 || p[i+1] != p[j+1])
            nextval[i] = j;
        else
            nextval[i] = nextval[j];
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值