数据结构——KMP算法详解(严蔚敏书版)

什么是KMP算法

KMP算法是一种用于字符串匹配的算法,其名字的由来是三位发现者的首字母加起来,没有什么特殊含义。这种算法总的来说就是在一个文本的指定位置之后找到一个需要的子串并返回它的位置,如果不存在指定的子串,则返回0.在KMP算法被发现之前,我们通用的算法一般都是建立双循环,所以时间复杂度一般在O(m*n).m和n是分别为文本串和子串的长度。而如果使用KMP算法,则可以将时间复杂度降低到O(n+m);既然这种算法如此快,那么我们就来看看其原理以及如何实现吧。

KMP算法的原理以及实现

这里我们假设文本串为“abbabbaaf”,模式串(需要寻找的子串)为“abbaaf”。聪明的读者可能一眼就看出来了,模式串在文本串的第四位找到匹配,因此我们需要做到的就是写一个函数,返回第四位就好了。如果按照我们正常的思路,很容易写出:

#include<iostream>
using namespace std;

int index(string text,string mode,int pos)
{
	int i=pos-1;
	int j=0;
	while(j<mode.length()&&i<text.length())
	{
		if(text[i]==mode[j]){++i;++j;}
		else {
			i=i-j+1;
			j=0;
		}
		cout<<text[i]<<" "<<mode[j]<<endl;
	}
	if(j==mode.length())
	return i-j+1;
	return 0;
}


int main()
{
	string text="aabaabaaf";
	string mode="aabaaf";
	cout<<index(text,mode,1)<<endl;
	return 0;
}

运行结果:
在这里插入图片描述
我们仔细分析代码,当模式串字符与文本字符发生不匹配时,我们的做法是将指向模式串的指针重新指向模式串的第一位元素,而文本串的指针指向下一位元素,正是在这个“回溯”的过程中,我们耗费的大量的时间。而KMP正是这样一种算法:当发生不匹配时,我们不会移动文本串的指针,而是只移动模式串的指针来达到重新匹配的效果。这里我们自然会想到,如何移动模式串的指针来达到这种效果呢?别急,我们先来仔细分析一下KMP算法的原理。
还是那个例子,假设我们有文本串(text)“aabaabaaf”,模式串(mode)“aabaaf”.文本串的指针为i,模式串指针为j。根据我们的思路,将文本串和模式串依次比较,发现在i和j都等于5时,发生不匹配的问题。如果按照传统思路,那么我们的匹配如图:
请添加图片描述
我们可以发现,其实在发现i=5和j=5之后的两次对比都可以省略,因为在第一次对比中我们知道文本串和模式串的前五个元素(这一段我们称子串)相等,观察模式串可以知道,在子串即“aabaa”中,前两个元素“aa”等于后两个元素“aa”。所以我们可以知道模式串中的子串的前两个元素等于文本串中的子串的后两个元素,所以我们可以直接让模式串中子串的前两个元素对其文本串中子串的后两个元素即可。在这个思想中,我们并未移动指向文本串的指针,仅仅移动了模式串的指针。
好了,例子说完了,这里我们将其抽象出来:对于一个文本串“s1s2s3…sn”,和一个模式串“p1p2p3…pm”,在文本串串的第i个元素和模式串的第j个元素发生不匹配现象时,如果在模式串中前j个元素中存在‘p1p2p3…pk-1’=='pj-k+1pj-k+2…pj-1’时(<1k<j),我们就会将值等于j的模式串指针指向k。这里我们可以注意到,这个k值只与模式串的特性有关,于是我们对于任意一个长度为len的模式串mode,当第j个模式串元素与文本串元素不匹配时,都有next[j-1]=k。这个next数组来存储在发生不匹配时我们指向模式串当前元素的指针在一次匹配时应该等于的值。于是我们有如下代码:

int KMP(string text,string mode,int pos)
{
	int i=pos;
	int k=1;
	int *next=new int[mode.length()];
	while(i<text.length()&&k<mode.length())
	{
		if(k==0||text[i-1]==mode[k-1])
		{
			++i;
			++k;
		}
		else k=next[k];
	}
	if(k>mode.length())
	{
		return i-k+1;
	}
	return 0;
}

到了这里,重点就变成了如何获得next数组

next数组的原理以及实现

首先我们再次说明next数组的定义:next[j]=k是指对于模式串在第j个位置的元素发生不匹配的情况时,指向j的指针下一步应该指向k。这里我们采用一个递归的思想来求得一个模式串的next数组。
首先我们有next[1]=0,而当next[j]=k时,意味着在模式串中有:p1p2p3…pk-1==pj-k+1pj-k+2…pj-1.如此便会出现两种情况
1、pk=pj,在这种情况下next[j+1]=next[j]+1.
2、pk≠pj,在这种情况下,我们可以将其看成一种字符串匹配问题,只不过现在该串既是模式串也是文本串。在现在匹配的过程中,已经有pj-k+1=pk-1,pj-k+2=pk-2…pj-1=pk-1.当发生pk≠pj,我们应该将j指向的元素与next[k]指向的元素进行对比,如果next[k]=k’,且pj=pk’,那么next[j+1]=k’+1=next[k]+1.
所以我们有以下代码

void get_next(string mode,int next[])
{
	//next[j]=k表示当模式串第j位不匹配时,模式串需要滑动到模式串第k位
	next[1]=0;
	int j=1,k=0;
	while(j<=mode.length())
	{
		if(k==0||mode[j-1]==mode[k-1]){++j,++k;next[j]=k;}
		else{
			k=next[k];
		}
	}
}
总结

KMP算法目前我也处于才学阶段,还有很多东西讲不清楚,希望大家一起来讨论,同时在我有新思路时也会更新此贴

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值