C++ KMP算法

本文介绍了KMP算法的起源、暴力(BF)算法的原理,以及KMP算法的优势。KMP算法通过预处理得到一个数组p[j],用于在模式串匹配失败时避免不必要的比较,提高效率。文章提供了C++代码实现,并推荐了一道KMP算法的模板题。

目录:

 

KMP的起源

BF算法

KMP算法

模板题推荐 



KMP的起源:

         KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(KMP就是取三个人名字的首字母作为该算法的名字)。KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。

        简而言之,就是三位信息界的Orz受够了暴力这种低效率的算法,共同研究了KMP这种优化算法,当然,KMP算法的效率要远远高于暴力,至于为什么,请读者大大继续往下看。

BF算法:

        BF算法,即暴力(Brute Force)算法,是普通的模式匹配算法,BF算法的思想就是将目标串 s 的第一个字符与模式串T的第一个字符进行匹配,若相等,则继续比较 s 的第二个字符和 t 的第二个字符;若不相等,则比较 s 的第二个字符和T的第一个字符,依次比较下去,直到得出最后的匹配结果。BF算法是一种蛮力算法。

        正所谓暴力,当然是:将主串 s 中某个位置i起始的子串和模式串t相比较。即从 j=0 起比较 s[i+j] 与 t[j],若相等,则在主串 t 中存在以 i 为起始位置匹配成功的可能性,继续往后比较( j逐步+1 ),直至与 t 串中最后一个字符相等为止,否则改从 s 串的下一个字符起重新开始进行下一轮的"匹配",即将串 t 向后滑动一位,即 i 增1,而 j 退回至0,重新开始新一轮的匹配。

【代码实现】

int baoli(char *s,char *t)//主串s,模式串t
{
    int i=0,j;
    while(i<strlen(s))
    {
        j=0;
        while(s[i]==t[j]&&j<strlen(t))//匹配过程
        {
          i++;//主串右移
          j++;//模式串右移
        }
        if(j==strlen(t))//匹配成功
        {
            return i-strlen(t);
        }
        i=i-j+1;//回溯过程
  }
  return -1;
}

        大家都知道,上面的暴力算法是很慢很慢的。你问我为什么?当然是因为它是暴力啦 因为暴力算法中有很多次匹配是重复的。

        例如:在串 s =”abcabcabdabba”中查找 t =” abcabd”(我们可以假设从下标0开始):先是比较 s[0] 和 t[0] 是否相等,然后比较 s[1] 和 t[1] 是否相等…我们发现一直比较到 s[5] 和 t[5] 才不等。当这样一个失配发生时,T下标必须回溯到开始,S下标回溯的长度与T相同,然后S下标增1,然后再次比较。这次立刻发生了失配,t 下标又回溯到开始,s 下标增 1 ,然后再次比较。又一次发生了失配,所以 t 下标又回溯到开始,s 下标增1,然后再次比较。这次 t 中的所有字符都和 s 中相应的字符匹配了。函数返回 t 在 s 中的起始下标3。

        数据稍微大那么一点点,暴击就会超时,于是,就有了KMP算法。

KMP算法:

        KMP算法其实就是一种改进的字符串匹配算法,关键是利用匹配后失败的信息,尽量减少模式串 t 与主串 s 的匹配次数以达到快速匹配的目的。

        对于两个字符串,s 表示主串,t 表示模式串,我们用两个指针 i 和 j 分别表示s[i-j+1...i]与t[1...j]完全相等。

        KMP的策略就是调整 j 的位置(减小 j 的值)并使 j 尽量大,使得s[i-j+1...i]与t[1...j]保持匹配且尝试匹配s[i+1]与t[j+1]。

        如果主串 s 为:a b c a b c d h i j k
        模式串 t 为:a b c e

        当匹配到主串 s 的第四个字符 A 时,可知 A 和 E 不相等,因此需要移向下一位,但并不需要从模式串中的第一位重新开始比较, 因为主串中的前三个字符已经没有未匹配的 A 了, 不可能匹配成功。

        可以发现,j 的取值与 i 无关,只与模式串 t 有关。这样,就可以预处理出一个数组 p[j] ,p[j] 应该是所有满足 t[1...k] = t[j-k+1...j] 的 k(k < j) 的最大值(即最长的相同的前缀后缀),表示当匹配到 t 数组的第 j 个字母而第 j+1 个字母不能完全匹配时,新的 j 最大是多少。

        实际上,有可能 j=0 时仍然存在 s[i+1]≠t[j+1]。因此增加 i 的值但忽略 j ,直到 s[i]=t[1]为止。

【代码实现】

inline void kmp()//假设主串 s 的下标从 1 开始
{
	for(int i=1,j=0;i<=strlen(s+1);i++)
	{
		while(j>0&&t[j+1]!=s[i])
		{
			j=p[j];
		}
		if(t[j+1]==s[i])
		{
			j++;
		}
		if(j==strlen(t+1))
		{
			sum++;//记录匹配成功的字串数量
		}
	}
}

        现在还剩下一个问题:怎么求 p[i] ?

        实际上,我们可以通过 p[1],p[2],... ,p[j-1] 的值来求 p[j] 的值。代码和主程序神似(其实就是模式串 t "自我匹配"的过程)

【代码实现】

int p[100001];
inline void pre()//假设模式串 t 的下标从 1 开始 
{
	
	p[1]=0;//p[1]的值初始为 0
	for(int i=1,j=0;i<strlen(t+1);i++)//匹配过程
	{
		while(j>0&&t[j+1]!=t[i+1])
		{
			j=p[j];
		}
		if(t[j+1]==t[i+1])
		{
			j++;
		}
		p[i+1]=j;
	}
}

模板题推荐: 

        小编在这里给大家推荐一道KMP的模板题,有心的OIers可以去做一下。

                                  洛谷 P3375【模板】KMP字符串匹配

这里顺便把小编我的题解展示一下:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<map>
#include<vector>
#include<string>
#include<cstring>
#include<queue>//头文件
using namespace std;
char a[1000001],b[1000001];//主串s->a[],模式串t->b[]
int p[1000001];
int qwq[1000001];//记录合法子串的开始位置
int tot;//记录查找到了几个合法子串
void pre()//p数组的预处理
{
	p[1]=0;//p[1]初始化为0
	for(int i=1,j=0;i<strlen(b+1);i++)//b[]的自我匹配过程
	{
		while(j>0&&b[i+1]!=b[j+1])
		{
			j=p[j];
		}
		if(b[i+1]==b[j+1])
		{
			j++;
		}
		p[i+1]=j;
	}
}
void kmp()//KMP过程
{
	for(int i=1,j=0;i<=strlen(a+1);i++)
	{
		while(j>0&&b[j+1]!=a[i])
		{
			j=p[j];
		}
		if(b[j+1]==a[i])
		{
			j++;
		}
		if(j==strlen(b+1))//符合则记录当前子串的开始位置
		{
			qwq[++tot]=i-strlen(b+1)+1;//不明白可以手推一下
		}
	}
}
int main()
{
	scanf("%s%s",a+1,b+1);//输入
	pre();
	kmp();
	for(int i=1;i<=tot;i++)//输出子串开始位置
	{
		printf("%d\n",qwq[i]);
	}
	for(int i=1;i<=strlen(b+1);i++)//最长前缀长度其实就是p[]
	{
		printf("%d ",p[i]);
	}
	return 0;
}

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值