KMP算法

算法简介

        KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现,因此人们称它为克努特---莫里斯---普拉特操作。KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配信息。时间复制度是O(m+n)。

导入

        对于字符串匹配算法大家第一个想到的可能就是暴力匹配(我就是这么想的),比如一个主串:ABABCABABA,模式串ABABA


定义一个指向主串s的指针i,一个指向模式串的指针j,上面可以看出,每次如果未匹配都需要回溯指针i,其中有很多的回溯都是不必要的,浪费太多的时间,能不能找到一种方法,不需要回溯指针i,用较少的时间开销来进行匹配呢,嗯这就是看毛片算法啦(KMP)。要想了解KMP算法我们需要先知道Next数组,也可以叫做prefix_table。

prefix_table求解

    prefix_table只和模式串有关和主串无关,假设模式串为T:ABABA


    前缀表的求解过程


下标是0的地方恒填入0,然后依次分割模式串,比如这里面,分割成AB、ABA、ABAB、ABABA分别找出他们的最长前缀和后缀的长度,例如:对于ABAB,他的最长前缀是ABA、最长后缀是BAB,比较后发现他们不匹配,那么找前缀为AB、后缀就为BA发现还是不匹配,则找前缀A、后缀A发现匹配,所以此处的长度就是1;

下面是prefix_table的代码

void prefix_table(char pattern[], int prefix[], int length)
{
	prefix[0] = 0;
	int len = 0;
	int i = 1;
	while (i < length)
	{
		if (pattern[i] == pattern[len])
		{
			len++;
			prefix[i] = len;
			i++;
		}
		else
		{
			//斜对一格,处理最后一次匹配,见注(1)
			if (len > 0)//避免数组越界
			{
				len = prefix[len-1];
			}
			//处理第一次匹配AB卡死情况;
			else
			{
				prefix[i] = len;
				i++;
			}
		}
	}
}

注(1):此出涉及到一个问题的处理,我们假设现在的模式串改成了ABABAA,考虑最后一次匹配的过程


此时的len==3,说明前面匹配的最长公共前后缀是3,也就是ABA,如果此时我们用pattern[len]和patter[5]比较会发现不等,我们直接填入0,但是发现最后一个A是可以和最前面的A匹配的,所以此时需要一点策略,可以采用斜对角的方式

len = prefix[len-1],如果还是不等,继续向前迭代,总会遍历到所有的模式串len长度前的所有字符的,这样就解决了最后一次的冲突问题。

为了后续KMP算法实现的简化,我们优化一个prefix_table把,prefix_table整体右移一位,并另前面prefix_table[0] == -1;


右移动代码

void move_prefix_table(int prefix[],int n)
{
	for (int i = n-1; i > 0; i--)
	{
		prefix[i] = prefix[i-1];//整体向右边移动一位
	}
	prefix[0] = -1;//把第一个prefix_table[0]的值设置成-1
}

KMP算法

void kmp_search(char s[], char pattern[])
{
	int n = strlen(pattern);
	int m = strlen(s);
	int *prefix = new int[n];

	prefix_table(pattern, prefix, n);
	move_prefix_table(prefix,n);

	int i = 0, j = 0;
	while (i < m)//主串指针小于主串长度,因为KMP算法i指针不回溯
	{
		if (j == n - 1 && s[i] == pattern[j])
		{
			cout << "located in " << i - j << endl;//匹配到
			j = prefix[j];//如果主串中还有相同的,继续后续匹配
		}
		if (s[i] == pattern[j])//比较主串和模式串的字符,如果相等则把i和j指针右移一位继续比较
		{
			i++;
			j++;
		}
		else
		{
			j = prefix[j];//未匹配s[i]和patter[j],那么把j指针移动到prefix_table中数值对应的位置上继续后续匹配
			
			if (j == -1) //匹配到prefix[0] == -1这个位置,需要把i和j指针整体右移一位
			{
				j++;
				i++;
			}
		}
	}
}
测试代码
int main()
{
	char pattern[] = "ABABA";
	char s[] = "ABABCABABA";//下标是5的位置匹配上了

	kmp_search(s, pattern);

	return 0;
}








        

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值