字符串匹配 - KMP算法

首先大致的学习一下有限自动机字符匹配算法,然后在讨论KMP算法。

有限自动机

一个有限自动机M是一个五元组(Q,q0,A,Σ,δ),其中:

  • Q是状态的集合,
  • q0∈Q是初始状态,
  • A是Q的字集,是一个接受状态集合,
  • Σ是一个有限的输入字母表,
  • δ是一个从Q×Σ到Q的函数,叫做转移函数。

下面定义几个相关函数:

  • φ(w)是M在扫描字符串w后终止时的状态。函数φ有下列递归关系定义:φ(ε) = q0,φ(wa) = δ(φ(w),a),
  • σ(x)是x的后缀中,关于P的最长前缀的长度。

字符串匹配自动机

来回顾一下朴素算法。给定下面两个字符串,模式串P,和匹配串T。

i 0 1 2 3 4 5 6 7 8 9 10
P a b a b a c a        
T a b a b a b a c a b a

当第一次匹配时,i=0,但是扫描到i=5的时候,字符串不在匹配。此时另i=1,重新匹配。这就是朴素算法需要改进的地方。当i=5的时候,观察表格发现P[0...3]=T[2...5],此时如果能够匹配T[5+1]和P[3+1]就不需要从i=2开始扫描了,效率就大大的提升了,这样匹配的时间复杂度就只有O(n)了。这里P[0...3]叫做P的前缀,T[2...5]叫做T5的后缀。此时σ(T5) = 3。这样在自动机的操作中,如果每次状态转移都能够保证:

        φ(Ti)=σ(Ti)

那么就可以保证最终的正确匹配。下面来做简单的推理:

根据φ(x)的定义,有φ(Tia) = δ(φ(Ti),a),其中a为任意字母;

由φ(Ti)=σ(Ti),可以得到φ(Tia)=σ(Tia) = q,即φ(Tia)=σ(Pqa);

综上,δ(φ(Ti),a)=σ(Pqa),,可以得到一个状态转移函数δ(q,a)=σ(Pqa)。这样就可以做出一个正确的状态转移图,然后就可以匹配字符串了。

用文字来描述一下:在自动机中,状态q就是Ti的后缀在P的最长前缀的长度。这样每次能够满足这个条件,就能够保证算法的正确进行。这里,在《算法导论》中有详细的数学证明。

KMP算法

KMP算法不建立一个有限自动机,但是必须要构建一个前缀函数,这里就叫做前缀数组吧。模式P和自己先匹配,得到前缀数组。前缀数组其实保存的就是自动机中的σ(x)的值。这样预处理的时间复杂度和自动机比就减少了很多。

预处理

给定模式P:

i 0 1 2 3 4 5 6 7 8 9
P a b a b a b a b c a
next 0 0 1 2 3 4 5 6 0 1

这里Pi[next[i]]表示的是Pi的关于P的最长后缀,P[i]表示P关于Pi的前缀。

当i=0时:

P0和P比较,P0[0] != P[0],所以next[0]=0;

当i=1时:

P1和P比较,P1[0] != P[1],所以next[1]=0;

当i=2时:

P2和P比较,P2[0] = P[2],所以next[2]=1;

当i=3时:

P3和P比较,P3[1] = P[3],所以next[3] = 2;

如此这般,就可以求得next数组了。一般算法描述数组都是从1开始,但是写代码的时候,数组是从下标0开始的,所以上面的next数组的每一个值都应该减一。next[i]=-1表示没有前缀匹配。这样在写代码的时候,应该是这样的:

i 0 1 2 3 4 5 6 7 8 9
P a b a b a b a b c a
next -1 -1 0 1 2 3 4 5 -1 0

当i=0时,初始化next[0] = -1;

当i=1时,(P1[next[0]+1] = a) != (P[1] = b),next[1] = -1;

当i=2时,(P2[next[1]+1] = a) != (P[2] = a),next[2] = 0;

当i=3时,(P3[next[2]+1] = b) !=(P[3] = b),next[3] = 1;

...

这样就不难发现next数组的作用了,记录了当前的σ(Pi)。Pi[next[i]+1] = P[i],就表示Pi最长前缀加一个字母和P的后缀加一个字母是否匹配。此时有两种情况:

  • Pi[next[i]+1] = P[i],这个时候σ(Pi+1) = σ(Pi) + 1,继续
  • Pi[next[i]+1] != P[i],这个时候没有直接从Pi[0]是不是等于P[i]扫描,而是从Pi[next[next[i]]+1]开始扫描。因为目前一定可以保证Pi[next[next[i]]是P的一个前缀。

下面是C代码的实现的求next数组:

void get_next(char *P, int next[],int len)
{
	printf("len=%d\n",len);
	next[0] = -1;
	int q = -1;
	int i;
	for(i = 1; i < len; i++) {
		while(q > 0 && P[q+1] != P[i]) { /* 判断P[q+1]适合等于P[i] */
			q = next[q]; /* 如果不相等, 一直找到满足条件的最长后缀 */
		}
		if(P[q+1] == P[i]) q++; /* 如果相等,那么很好,继续... */
		next[i] = q;

	}
}

匹配

当求出next数组后,就可以进行字符串匹配了。匹配的方法和求next的方法相识。下面是完整的代码:

/*************************************************************************
    > File Name: KMP.c
    > Author: mr_zys
    > Mail: [email protected]
    > Created Time: 2014年10月09日 星期四 14时48分30秒
 ************************************************************************/

#include<stdio.h>
#include<string.h>
#define maxn 100
int next[maxn];
char P[maxn],T[maxn];

void get_next(char *P, int next[],int len)
{
	printf("len=%d\n",len);
	next[0] = -1;
	int q = -1;
	int i;
	for(i = 1; i < len; i++) {
		while(q > 0 && P[q+1] != P[i]) { /* 判断P[q+1]适合等于P[i] */
			q = next[q]; /* 如果不相等, 一直找到满足条件的最长后缀 */
		}
		if(P[q+1] == P[i]) q++; /* 如果相等,那么很好,继续... */
		next[i] = q;

	}
}
void KMP(char *P, char *T)
{
	int len_P = strlen(P);
	int len_T = strlen(T);
	int j = -1;
	int i;
	for(i = 0; i < len_T; i++) {
		while(j > -1 && T[i] != P[j+1]) {
			j = next[j];
		}
		if(P[j+1] == T[i]) {
			j++;
			//printf("%d %d\n",j,i);
		}
		if(j == len_P-1){
			printf("在%d处开始匹配\n",i-len_P+1);
			j = next[j];
		}
	}
}
int main()
{
	printf("input the string P:\n");
	scanf("%s",P);
	printf("input the string T:\n");
	scanf("%s",T);
	printf("%s\n",P);
	get_next(P,next,strlen(P));
	int i;
	for(i = 0; i < strlen(P); i++) {
		printf("(%d)",next[i]);
	}
	printf("\n");
	KMP(P,T);
	return 0;
}

可能,中间有些表述不清,求指正哈!

-end-

时间: 2024-10-21 14:17:00

字符串匹配 - KMP算法的相关文章

字符串匹配KMP算法C++代码实现

看到了一篇关于<字符串匹配的KMP算法>(见下文)的介绍,地址:http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html,这篇博客对KMP算法的解释很清晰,但缺点是没有代码的实现.所以本人根据这位大神的思路写了一下算法的C++实现. C++代码如下: #include <iostream> #include<string.h> using namesp

字符串匹配--kmp算法原理整理

kmp算法原理:求出P0···Pi的最大相同前后缀长度k: 字符串匹配是计算机的基本任务之一.举例,字符串"BBC ABCDAB ABCDABCDABDE",里面是否包含另一个字符串"ABCDABD"? 许多算法可以完成这个任务,Knuth-Morris-Pratt算法(简称KMP)是最常用的之一. KMP算法搜索如下: 1.首先,字符串"BBC ABCDAB ABCDABCDABDE"的第一个字符与搜索词"ABCDABD"的

字符串匹配KMP算法的理解(详细)

1. 引言 本KMP原文最初写于2年多前的2011年12月,因当时初次接触KMP,思路混乱导致写也写得混乱.所以一直想找机会重新写下KMP,但苦于一直以来对KMP的理解始终不够,故才迟迟没有修改本文. 然近期因开了个算法班,班上专门讲解数据结构.面试.算法,才再次仔细回顾了这个KMP,在综合了一些网友的理解.以及算法班的两位讲师朋友曹博.邹博的理解之后,写了9张PPT,发在微博上.随后,一不做二不休,索性将PPT上的内容整理到了本文之中(后来文章越写越完整,所含内容早已不再是九张PPT 那样简单

字符串匹配KMP算法

1. 字符串匹配的KMP算法 2. KMP算法详解 3. 从头到尾彻底理解KMP

字符串匹配-KMP算法学习笔记

参考文章: 1.字符串匹配的KMP算法 2.KMP算法详解 3.从头到尾彻底理解KMP 版权声明:本文为博主原创文章,未经博主允许不得转载.

数据结构与算法简记--字符串匹配KMP算法

KMP算法 比较难理解,准备有时间专门啃一下. 核心思想与BM算法一样:假设主串是 a,模式串是 b.在模式串与主串匹配的过程中,当遇到不可匹配的字符的时候,我们希望找到一些规律,可以将模式串往后多滑动几位,跳过那些肯定不会匹配的情况. 不同的是:在模式串和主串匹配的过程中,把不能匹配的那个字符仍然叫作坏字符,把已经匹配的那段字符串叫作好前缀. 关键找相等的最长匹配前缀和最长匹配后缀.有两种情况,(1)如果b[i-1]的最长前缀下一个字符与b[i]相等,则next[i]=next[i-1]+1.

字符串匹配KMP算法实现

由于KMP算法比较难,所以建议初学者分两个阶段学习. 第一个阶段先理解算法思想,可以参考这篇文章:点击打开链接 第二个阶段,理解算法的具体实现,本文主要讲解这部分,需要注意的地方都在程序里了,自己看吧 程序(调试通过): #include <stdio.h> #include <string.h> int KMP(char* s, char* pattern, int start, int next[]); void get_new_next(char* pattern, int

【数据结构与算法】字符串匹配KMP算法

首先需要了解一下BF暴力匹配算法,这个算法为每一个串设置一个指针,然后两个指针同时后移,出现不匹配的情况后,主串指针回到开始后移之前的位置的下一位,模式串指针回到最开始. 对比一下KMP算法,同样是设置两个指针,然后两个指针同时后移,出现不匹配的情况后,主串指针不变,模式串指针回溯一定的距离.具体模式串指针回溯多少,是第一次看KMP算法的人比较难以理解的,其实仔细想想,模式串的前缀和后缀其实也是在做匹配,当P[K]!=P[J]时就是失配,那么前缀的指针就需要回溯,所以后k=next[k]. 代码

字符串匹配——KMP算法(C++)

源代码: #include<cstdio> #include<cstring> #include<iostream> using namespace std; string s1,s2; int m,n,k(0),next[1001]; //在Next数组中,存储的是匹配失败后,上一位应该跳跃到的节点编号. int main() { getline(cin,s1); getline(cin,s2); m=s1.size(); n=s2.size(); next[0]=0