串的匹配:朴素匹配&kmp算法

mthoutai mthoutai     2022-08-29     387

关键词:

引言

字符串的模式匹配是一种经常使用的操作。

模式匹配(pattern matching),简单讲就是在文本(text,或者说母串str)中寻找一给定的模式(pattern)。通常文本都非常大。而模式则比較短小。典型的样例如文本编辑和DNA分析。

在进行文本编辑时,文本一般是一段话或一篇文章,而模式则经常是一个单词。若是对某个指定单词进行替换操作,则要在整篇文章中进行匹配,效率要求肯定是非常高的。

模式匹配的朴素算法

最简单也最easy想到的是朴素匹配。何为朴素匹配,简单讲就是把模式串跟母串从左向右或从右向左一点一点比較:先把模式串的第一个字符同母串的第一个字符比較,若相等则接着比較后面的相应字符。若不等。把模式串后移一个位置,再次从模式串的头部比較……

这如同枚举法:把母串中与模式串同样长度的子串挨个比較。则这样的匹配方式显然无不论什么启示性或智能性。

例如以下图:

技术分享

以上步骤非常easy看懂。它的代码也各种各样,以下是当中一种:

/*
朴素的模式匹配算法,匹配方向:从前往后
匹配成功,则返回匹配成功时主串的下标位置(仅仅返回第一次匹配成功的位置)
否则,返回-1
母串或子串有一个为空也返回-1
*/
int naiveStringMatching(const char* T, const char* P)
{
	if (T && P)
	{
		int i, j, lenT, lenP;
		lenT = strlen(T);
		lenP = strlen(P);
		//模式串的长度比主串还长,显然无法匹配
		if (lenP > lenT)
			return -1;
		i = 0;
		while (i <= lenT-lenP)
		{
			j = 0;
			if (T[i] == P[j])
			{
				j++;
				//指针的写法是这种:while(j < lenP && *(T + i + j) == *P(j))j++;
				while (j < lenP && T[i + j] == P[j])
					j++;
				//顺利匹配到了模式串的结尾,则匹配成功
				if (j == lenP)
					return i;
			}
			i++;
		}
		//假设程序执行到这里,仍然没有结束,说明没有匹配上
		return -1;
	}
	return -1;
}

考虑到有时须要使用c++中的string类型,这时它的代码是这种:

int naiveStringMatching(const string T, const string P)
{
	int i, j, lenT, lenP;
	lenT = T.length();
	lenP = P.length();
	//串空或模式串的长度比主串还长,显然无法匹配
	if (lenT == 0 || lenP == 0 || lenP > lenT)
		return -1;
	i = 0;
	while (i <= lenT - lenP)
	{
		j = 0;
		if (T[i] == P[j])
		{
			j++;
			while (j < lenP && T[i + j] == P[j])
				j++;
			if (j == lenP)
				return i;
		}
		i++;
	}
	return -1;
}

不要小看上面的代码。尽管效率不高,但仍有掌握的必要。代码和算法总是在不断优化的,而这一切的优化都是从简单的情形開始的。

朴素匹配的时间复杂度是非常easy分析的。若是成功匹配。最好的情况下,第一次比較就匹配上了,此时仅仅需strlen(P)次(模式串的长度次)比較。最坏的情况下,一直须要比較到母串最后一个长度与模式串同样的子串。共(strlen(T)-strlen(P)+1)*strlen(P)次比較。平均下是O(strlen(T)*strlen(P))。


KMP算法

KMP算法是一种用于字符串匹配的算法,这个算法的高效之处在于当在某个位置匹配不成功的时候能够依据之前的匹配结果从模式字符串的还有一个合适的位置開始,而不必每次都从头開始匹配。该算法由Knuth、Morris和Pratt三人设计,故取名为KMP。

KMP的改进之处

回想一下上文讲的的朴素匹配算法。每次失配的时候,都是 i++;j=0; 从画面上看,就是把模式串相对于主串向后移动一个位置。再次从模式串的首字符開始新的一轮比較。用数学的形式讲,这是 i++;j=0; 的几何意义

失配时,记主串的下标为i,此时模式串的下标是j。

则能够肯定已有j个字符成功匹配。例如以下图:

Ti-j Ti-j+1   ...Ti-2 Ti-1 Ti

P0  P1   ...Pj-2 Pj-1 Pj         图(a)

在上图中,红色的字符是匹配上的。下标 0,1..j-1 不正好是j个吗?

KMP的做法是,充分利用已匹配的信息,失匹配时:i不变,j=next[j],当中next[j]<=j-1。即失配的时候,又一次用模式串的next[j]位置的字符和主串的i位置进行匹配。

故模式串相对主串移动的位置大小是j-next[j]>=1,在普通情况下都比朴素匹配的移动一个位置高效。这就是KMP的改进之处。

之所以敢把主串T[i]与模式串P[next[j]]直接匹配。也就是说跳过模式串的前next[j]个字符,那么以下的事实必须存在:

Ti-next[j]  Ti-next[j]+1 ...Ti-2    Ti-1   Ti

P0     P1     ...Pnext[j]-2 Pnext[j]-1 Pnext[j]           图(b)

这个事实就是:模式串下标从0到next[j]-1位置的字符已经匹配成功了

两个论断

(1)从图(a)中要明白一点:Ti-j...Ti-1和P0...Pj-1是一模一样的。所以把前者看成后者是没有问题的。

(2)从图(b)中能够得到,P0...Pnext[j]-1 是 P0...Pj-1 的前 next[j] 个连续字符子串,而 Ti-next[j]...Ti-1 也就是 Pj-next[j]...Pj-1(依据上一条结论)是 P0...Pj-1 的后next[j]个连续字符子串。而且它们是一模一样的(相应匹配)。

前缀、后缀

这就引出了前缀、后缀的概念。举一个样例就可以说明:

字符串 "abc"

它有前缀  "a"  "ab"  "abc"

它有后缀  "c"  "bc"  "abc"

看到这里,不用过多的解释,大家也可明确前后缀指的是什么呢。

next[j]的含义

结合前后缀的概念,可得出next[j]的的实际含义:next[j]就是模式串下标 0...j-1 这j个字符的最长相相应前缀和后缀的长度。

非常显然。相相应是指能够匹配得上。至于为什么是最长?,基于两点考虑:

(i)直观上看,next[j]最大。说明剩下须要进行匹配验证的字符就最少嘛。

(ii)本质上,仅仅能取最大,否则会遗漏可能的匹配。(这一点,须要细致想想!

)

须要指出。这里我们仅仅能考虑非平庸的前后缀。否则,对于平庸的无意义。

(平庸前后缀是指:空串和串本身。

其他的都是非平庸的。

)

另一点我们得明确:next数组全然由模式串本身确定。与主串无关

求解next数组的步骤

  1. 先求出以当前字符结尾的子串的最长相相应前缀和后缀的长度。
  2. 当前字符的next值,须要參考(參考就是取的意思)上一个字符的步骤1中的求解结果。

    至于第一个字符,因为没有“上一个字符”的说法。直接设置为-1。就可以。

看一个详细的样例:
模式串 "ababca"
最长前后缀长度表
模式串 a b a b c a
最长前后缀长度 0 0 1 2 0 1

由上表得出next数组
下标 0 1 2 3 4 5
next -1 0 0 1 2 0


直观地看就是:把“最长前后缀长度表”右移一个位置,于是最右边的一个长度被丢弃掉了,最左边空出的填上-1。这样得到的就是next数组。


next数组的递推求解


当模式串的长度非常小的时候。手动计算next数组也没什么问题。

可手动计算终究不是办法,必须机器计算。实际上next数组能够递推求解。这也是一个理解上的难点。

(i)初始 next[0]=-1;
(ii)若next[j]为k,即有 P0...Pk-1(Pk...)=Pj-k...Pj-1(Pj...)(*式)(‘=‘的意义:相应位置相匹配)。分两种情况。求解next[j+1]:
  1. if(Pk==Pj),则 next[j+1]=k+1=next[j]+1; 道理显而易见:若PkPj相等,则最长前后缀顺势增长一个,由*式能够看出。

  2. PkPj不相等,则更新 k=next[k];if(Pk==Pj) next[j+1]=k+1;否则。反复此过程。

    (这里也是个匹配问题)

我们用手动计算得到的next数组,来进行下測试:
给定一主串 "cadabababcacadda"。模式串 "ababca",next数组同上:next[]={-1,0,0,1,2,0}
技术分享
技术分享

代码

#include<iostream>
#include<iomanip>
using namespace std;
/*
依据模式串P,设置next数组的值
*/
void setNext(const char* P, int* next)
{
	int j, k, lenP;
	lenP = strlen(P);
	j = 1;
	next[0] = -1;
	while (j < lenP)
	{
		k = next[j-1];
		//P[j]!=P[k]
		while ((k >= 0) && P[j-1]!=P[k])
			k = next[k];
		if (k < 0)
			next[j] = 0;
		else
			next[j] = k + 1;
		j++;
	}
}
/*
串的模式匹配:KMP算法
(i)T是主串,其形式是字符串常量或者是以‘‘结尾的字符串数组,如"abc"或{‘a‘,‘b‘,‘c‘,‘‘}
(i)P是子串。其形式和主串一样
(i)next数组
(o)匹配成功,返回主串中第一次匹配成功的下标;否则,返回-1
*/
int KMP(const char *T, const char *P, const int *next)
{
	if (T && P)
	{
		//lenT是主串长度,lenP是子串长度
		int lenT, lenP;
		lenT = strlen(T), lenP = strlen(P);
		//主串长度小于子串,显然无法匹配
		if (lenT < lenP)
			return -1;
		int i, j, pos;
		i = j = -1;
		pos = lenT - lenP;  //i最多仅仅需变化到pos位置。想想?非常easy的
		while (i <= pos && j < lenP)
		{
			//匹配成功或第一次匹配
			if (j == -1 || T[i] == P[j])
			{
				i++;
				j++;
			}
			else//匹配失败
				j = next[j];
		}
		if (j == lenP)
			return i - lenP;  //这个返回值非常好理解
		else
			return -1;
	}
	return -1;
}
void print(int *array, int n)
{
	if (array && n > 0)
	{
		int i;
		for (i = 0; i < n; i++)
			cout << setw(4) << array[i];
		cout << endl;
	}
}
int main()
{
	cout << "***串的模式匹配:KMP算法***by David***" << endl;
	char T[] = "cadabababcacadda";
	char P[] = "ababca";
	cout << "主串" << endl;
	cout << T << endl;
	cout << "子串" << endl;
	cout << P << endl;
	int n = strlen(P);
	int *next=new int[n];
	setNext(P, next);
	cout << "打印next数组" << endl;
	print(next, n);
	cout << "使用KMP算法进行模式匹配" << endl;
	int index = KMP(T, P, next);
	if (index == -1)
		cout << "无法匹配!" << endl;
	else
		cout << "匹配成功。,匹配到的下标是 " << index << endl;
	delete[]next;
	system("pause");
	return 0;
}

执行

技术分享
 

代码下载:KMP算法


转载请注明出处。本文地址:http://blog.csdn.net/zhangxiangdavaid/article/details/35569257


若有所帮助,顶一个哦!


专栏文件夹:


















串的模式匹配算法之kmp(代码片段)

title:串的模式匹配算法之kmptags:数据结构与算法之美author:辰砂1.引言首先我们需要了解串的模式算法目的:确定主串中所含子串第一次出现的位置(定位);常见的算法种类:BF算法(又称古典的、经典的、朴素的、穷举的),KM... 查看详情

kmp算法(代码片段)

串的朴素模式匹配算法什么是字串匹配:在主串中找到与模式串相同的字串并返回其位置,如主串google、模式串gle,则结果为3算法思路:相当于拿着模式串和主串对齐,对比其第一个字符。不相等则模式串往... 查看详情

4.2串的模式匹配算法(代码片段)

 <?phpheader("content-type:text/html;charset=utf-8");classLinear_string/***串模式匹配算法**包括*1.串的初始化__contruct()*2.朴素的模式匹配算法index()*3.KMP模式匹配算法*4.改进的KMP模式匹配算法*/private$string;private$length;//构造函数p 查看详情

字符串的朴素模式匹配算法

#include<stdio.h>#include<string.h>//返回第一个子串在主串的位置,找不到返回-1intStrMatch(char*source,char*match){intslen=strlen(source);intmlen=strlen(match);inti=0,j=0;while(i<slen&&j<mlen){// 查看详情

字符串模式匹配kmp算法

...字符串中出现的位置。/*朴素的模式匹配算法功能:字符串的模式匹配参数:s:目标串p:模式串pos:开发匹配的位置返回值:匹配成功,返回模式串在目标串的其实位置匹配不成功,返回-1*/intmatch(constchar*s,constchar*p,intpos){ 查看详情

字符串匹配算法(bf算法&&kmp算法)(代码片段)

字符串匹配算法暴力匹配(BF)算法KMP算法next数组求next数组的练习next数组的优化(nextval数组)练习暴力匹配(BF)算法BF算法,即暴力(BruteForce)算法,是普通的模式匹配算法,BF算法的思想就是... 查看详情

java数据结构&算法宁可累死自己,也要卷死别人17kmp算法(代码片段)

...宁可累死自己,也要卷死别人17⚠️KMP算法概述KMP算法部分匹配表KMP算法实现概述从今天开始,小白我将带大家开启Java数据结构&算法的新篇章.KMP算法KMP(Knuth-Morris-Pratt),是一种改进的字符串匹配算法.KMP算法解决了暴力匹配需要高... 查看详情

[填坑][主线任务]mp&kmp算法

...组的含义吧(从0开始字符串下标):fail[i]表示0~i-1这个串的最长border的长度,同时也是重新开始匹配的将要匹配的那个位置下标。举个小栗子:对于字符串abcdabd fail[6]=2,即abcdab的最长border的长度为2(ab),匹配失 查看详情

串的模式匹配算法------kmp算法(代码片段)

1#include<stdio.h>2#include<stdlib.h>3#include<string.h>45int*get_next(chart[],intlength)67inti=0,j=-1;8int*next=(int*)malloc(length*sizeof(int));9next[0]=-1;10while(i<length)1112if(j==-1||t[i]==t[j])1314i++;15j++;16next[i]=j;1718else1920j=next[j];21222324returnnext;252627intInd... 查看详情

kmp&拓展kmp(代码片段)

KMP算法解析KMP算法是一种比较高效的字符串匹配算法,可以在线性时间内找出匹配位置和匹配长度。详解KMP板子(next)数组存在的意义:当(A)串匹配到(i),(B)串匹配到(j)时,如果发现失配,可以直接令(j=next[i])然后继续匹配,((next[... 查看详情

kmp子串查找算法

...的关键是利用已经部分匹配的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。在介绍KMP之前先说一说朴素解法,也就是最简单的暴力解法,朴素解法是采用穷举的方式一个一个对比达到查找的功能 查看详情

数据结构&算法-kmp匹配算法(代码片段)

运行结果代码usingSystem;namespaceKMPAlgorithmclassProgramstaticvoidMain(string[]args)stringmainstr="acdeebfgracacdacdefg";stringpatternstr="dacdef";Console.WriteLine("==& 查看详情

算法导论字符串匹配—朴素算法rabin-karp有限自动机kmp

...abin-Karp算法预处理时间:Θ(m),需要预先算出匹配串的哈希值匹配时间:O((n−m+1)m),匹配过程与朴素算法类似,但是不需要逐个比对,先比对哈希值,这样可以减少字符匹配次数,计算待匹配的m... 查看详情

kmp算法(代码片段)

...gt;>s+1>>m>>p+1;//KMP算法习惯下标从1开始//求模式串的next数组for(inti=2,j=0;i<=m;i++)while(j&&p[i]!=p[j+1])j=ne[j];if(p[i]==p[j+1])j++;ne[i]=j;//KMP匹配过程for(inti=1,j=0;i<=n;i++)while(j&&s[i]!=p[j+1])j=ne[j];if(s[i]==p[j+1])j++;if(j==m)//匹... 查看详情

第四章:2.串--串的模式匹配算法(kmp)

前言:   目录:  1.串类型的定义  2.串的表示和实现  3.串的模式匹配算法  4.串操作应用举例 正文:  串的模式匹配即,在给定主串S中,搜索子串T的位置,如果存在T则返回其所在位置,否则返回0  串... 查看详情

常用算法3-字符串查找/模式匹配算法(bf&kmp算法)

...么,我们有木有想过linux是如何在浩如烟海的文本中正确匹配到我们所需要的字符串呢?这就牵扯到了模式匹配算法!1.模式匹配什么是模式匹配呢?模式匹配,即子串P(模式串)在主串T(目标串)中的定位运算,也称串匹配假设我... 查看详情

kmp算法

...相同的时候,继续比较,当不匹配时,子串右移,使得子串的不匹配位置的最长前缀串移动到长串的不匹配位置左边,与之相邻。之后继续,如若一直不匹配,直到最长前缀串为0,则从子串第一位继续与不匹配位置比较。以此... 查看详情

kmp--字符串匹配

现在计算机处理涉及到大量的字符串操作,字符串的匹配是使用频率最高的字符串操作之一,大学数据结构与算法中字符串一章,也专门介绍了字符串匹配。字符串的单模式匹配中最基础的算法是朴素的模式串匹配算法,比这更... 查看详情