KMP算法浅析

       
1.BF算法

        BF算法是普通的模式匹配算法,BF算法的思想就是将目标串s的第一个字符与模式串t的第一个字符进行匹配,若想等,则比较s的第二个字符和t的第二个字符;若不想等,则比较s的第二个字符和t的第一个字符。如此一直比较下去,直到得出最后的匹配结果。

        在比较过程中每次匹配失败都要将标记s串当前比较位置的指针i回溯到最初开始比较的位置,然后后移一位,而目标串则要第一个字符开始。通过对比较过程进行分析可知,其中有些比较是多余的,可以避免。      

        2.KMP算法

        KMP算法与BF算法的区别在于KMP算法巧妙的消除了指针i的回溯问题,指针i不需要移动,只需要移动模式串t中下次需要匹配的位置j即可,使问题的时间复杂度降低。

        假定目标串为S[i],其中0 <= i <= n;模式串为T[j],其中0 <= j <= m。

        假设当前匹配到如下位置:

                S0,S1,S2…
Si-j,Si-j+1…………..Si-1,
Si,Si+1….Sn

                                    
T0,T1…………………Tj-1,
Tj……

        其中S和T中的红色部分匹配成功,恰好到Si和Ti的时候匹配失败,如果要保持i不变,同时要让模式串T相对于原始串S右移的话,可以更新j的值,让Si和新的Tj进行匹配,假设新的j用next[j]表示,即让Si和Tnext[j]匹配,显然新的j值要小于之前的j值,模式串才会是右移的效果,也就是说应该有next[j] <= j -1。那新的j值也就是next[j]应该是多少呢?观察如下的匹配:

        (1)如果模式串右移1位(从简单的思考起,移动一位会怎么样),即next[j] = j – 1, 即让绿色的Si和Tj-1匹配 (注:省略号为未匹配部分)

                S0,S1,S2,…,
Si-j,Si-j+1……………,Si-1,
Si, Si+1,….,Sn

                                     
T0,T1,…………………,Tj-1,
Tj, ………. (T的划线部分和S划线部分相等【1】)

                                          
T0,T1,……………..Tj-2,
Tj-1, ……. (移动后的T的划线部分和S的划线部分相等【2】)

        根据【1】【2】可以知道当next[j] =j -1,即模式串右移一位的时候,有T[0 ~ j-2] == T[1 ~ j-1],而这两部分恰好是字符串T[0 ~j-1]的前缀和后缀,也就是说next[j]的值取决于模式串T中j前面部分的前缀和后缀相等部分的长度(好好揣摩这两个关键字概念:前缀、后缀)。

        (2)如果模式串右移2位,即next[j] = j – 2, 即让绿色的Si和Tj-2匹配   

                S0,S1,…,
Si-j,Si-j+1,Si-j+2……………,Si-1,
Si, Si+1,….,Sn

                                     
T0,T1,T2,…………………,Tj-1,
Tj, ……….(T的划线部分和S划线部分相等【3】)

                                           
      T0,T1,……………,Tj-3,
Tj-2,………(移动后的T的划线部分和S的划线部分相等【4】)

        同样根据【3】【4】可以知道当next[j] =j -2,即模式串右移两位的时候,有T[0 ~ j-3] == T[2 ~ j-1]。而这两部分也恰好是字符串T[0 ~j-1]的前缀和后缀,也就是说next[j]的值取决于模式串T中j前面部分的前缀和后缀相等部分的长度。

        (3)依次类推,可以得到如下结论:当匹配失败的情况下,j的新值next[j]取决于模式串中T[0 ~ j-1]中前缀和后缀相等部分的长度, 并且next[j]恰好等于这个最大长度。因此,KMP算法的关键工作就是求得next[]数组,而且该数组只和模式串T有关,是模式串自身的特性。

       

        3.Next数组

        在KMP算法中,为了确定在匹配不成功时,下次匹配时j的位置,引入了next[]数组,next[j]的值表示P[0…j-1]中最长后缀的长度等于相同字符序列的前缀。对于next[]数组的定义如下:

        (1) next[j] = -1,其中j = 0时。

        (2) next[j] = max(k),其中0<k<j   P[0…k-1]=P[j-k,j-1]

        (3) next[j] = 0  其他情况

     如:

          P          a    b   a    b   a

          j            0    1   2    3   4

             next     -1   0   0    1   2

         即next[j]=k>0时,表示P[0…k-1]=P[j-k,j-1]

        KMP算法的思想就是:在匹配过程称,若发生不匹配的情况,如果next[j]>=0,则目标串的指针i不变,将模式串的指针j移动到 next[j]的位置继续进行匹配;若next[j]=-1,则将i右移1位,并将j置0,继续进行比较。

        KMP算法的关键在于求算next[]数组的值,即求算模式串每个位置处的最长后缀与前缀相同的长度, 而求算next[]数组的值有两种思路,第一种思路是用递推的思想去求算,还有一种就是直接去求解。

        (1)按照递推的思想求解next数组:

        根据定义next[0]=-1,假设next[j]=k, 即P[0…k-1]==P[j-k,j-1]

        1)若P[j]==P[k],则有P[0..k]==P[j-k,j],很显然,next[j+1]=next[j]+1=k+1;

        2)若P[j]!=P[k],则可以把其看做模式匹配的问题,即匹配失败的时候,k值如何移动,显然k=next[k]。

        (2)直接根据定义求解next数组

        4.时间复杂度分析

        BF算法的时间复杂度:在最坏的情况下,BF算法要将目标串的每一个字符同模式串进行比较一遍,假定目标串长度为m,模式串长度为n,总的时间复杂度为O(m*n)。而对于KMP算法,进行比较的时间复杂度为O(m+n),求next数组的时间复杂度为n,总体时间复杂度为O(m+2n)。

        5.源代码

#include <iostream>
#include <string>
#include <cstdlib>
#include <sys/time.h>

using namespace std;

const int X = 30000, Y = 20;

void getNext_1(string t ,int next[]);
void getNext_2(string t ,int next[]);
bool equals(string str, int i, int j);

//1.进行模式匹配的BF算法
int BF_Match(string s, string t){
	int slen = s.size();
	int tlen = t.size();
	if(slen >= tlen){
		for(int k = 0; k <= slen - tlen; k++){
			int i = k, j = 0;
			while(i < slen && j < tlen && s[i] == t[j]){
				i++;
				j++;
			}
			if(j == tlen)
				return k;
			else
				continue;
					
		}
	}	
	return -1;
}

//2.1进行模式匹配的KMP算法(调用getNext_1)
int KMP_Match_1(string s, string t){
	int slen = s.size();
	int tlen = t.size();
	if(slen >= tlen){
		int i = 0, j = 0;
		int next[Y];
		getNext_1(t, next);
		while(i < slen){
			if(j == -1 || s[i] == t[j]){
				i++;
				j++;
			}else{
				j = next[j];
			}
			if(j == tlen)
				return i - tlen;
		}
	}
	return -1;
}

//2.2进行模式匹配的KMP算法(调用getNext_2)
int KMP_Match_2(string s, string t){
	int slen = s.size();
	int tlen = t.size();
	if(slen >= tlen){
		int i = 0, j = 0;
		int next[Y];
		getNext_2(t, next);
		while(i < slen){
			if(j == -1 || s[i] == t[j]){
				i++;
				j++;
			}else{
				j = next[j];
			}
			if(j == tlen)
				return i - tlen;
		}
	}
	return -1;
}

//3.1用递推法求目标串对应的NEXT数组
void getNext_1(string t ,int next[]){
	int j, k;
	next[0] = -1;
	j = 0;
	k = -1;
	while(j < t.size() - 1){
		if(k == -1 || t[j] == t[k]){
			j++;
			k++;
			next[j] = k;
		}else{
			k = next[k]; 
		}		
	}
}

//3.2直接求解目标串对应的NEXT数组
void getNext_2(string t, int next[]){
	int i, j, tmp;
	next[0] = -1;
	next[1] = 0;
	for(i = 2; i < t.size(); i++){
		tmp = i - 1;
		for(j = tmp; j > 0; j--){
			if(equals(t, i, j)){
				next[i] = j;
				break;
			}
		}
		if(j == 0)
			next[i] = 0;
	}
}
bool equals(string str, int i, int j){
	int k = 0;
	int s = i - j;
	for(k = 0; k <= j - 1 && s <= i - 1; k++, s++){
		if(str[k] != str[s])
			return false;
	}
	return true;
}

//封装的不同的find函数
int find_1(string x, string y){
	return BF_Match(x, y);	
}

int find_2(string x, string y){
	return KMP_Match_1(x, y);
}

int find_3(string x, string y){
	return KMP_Match_2(x, y);
}

char* rand_string(char *str, int len){
	int i = 0;
	srand((unsigned)time(NULL));
	for(i = 0; i < len; i++)
		str[i] = rand()%26 + 'a';
	str[i] = '\0';
	return str;
}

long getCurrentTime(){
	struct timeval tv;
	gettimeofday(&tv, NULL);
	return tv.tv_sec * 1000 + tv.tv_usec / 1000;
}

int main(){
	string x = "afafafafafafaffadadadadad";
	string y = "dadadadad";
	/*
	char str_1[X+1];
	rand_string(str_1, X);
	string x(str_1);
	sleep(1);
	*/
	/*
	char str_2[Y+1];
	rand_string(str_2, Y);
	string y(str_2);
	*/
	cout << "string x = " << x << endl;
	cout << "  字符串长度为:" << x.length() << endl;
	cout << "string y = " << y << endl;
	cout << "  字符串长度为:" << y.length() << endl;
	
	long time_1 = getCurrentTime();
	int pos_1 = find_1(x, y);
	long time_2 = getCurrentTime();
	cout << "1.匹配结果" << pos_1;	
	cout << "  用时:";
	cout << time_2 - time_1 << " ms;" << endl;
	
	long time_3 = getCurrentTime();
	int pos_2 = find_2(x, y);
	long time_4 = getCurrentTime();
	cout << "2.匹配结果" << pos_2;	
	cout << "  用时:";
	cout << time_4 - time_3 << " ms;" << endl;

	long time_5 = getCurrentTime();
	int pos_3 = find_3(x, y);
	long time_6 = getCurrentTime();
	cout << "3.匹配结果" << pos_3;	
	cout << "  用时:";
	cout << time_6 - time_5 << " ms;" << endl;
}
    原文作者:KMP算法
    原文地址: https://blog.csdn.net/u010189459/article/details/28888773
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞