KMP模式匹配算法改进

一、简介

  上一篇介绍了标准的 KMP 模式匹配算法,但是这种算法在某种情况下存在一定的缺陷,会产生不必要的比较。
  改进后的 KMP 模式匹配算法进行匹配时,和标准 KMP 算法的流程一样。仅仅在求取 next 数组的值时,如果子串该位置的字符与 next 数组指向的内容相等,则该位置的 next 数组值修改为所指向位置的 next 数组值。

二、案例分析

  假设主串为 “aaaaadef”,子串为 “aaaaaf”,规定子串和主串的起始位置下标均为 1,主串的位置下标用 i 表示,子串的位置下标用 j 表示。标准 KMP 算法计算出来的 next[] = {0 1 2 3 4 5},当 i=6, j=6 时,主串的字符 “d” 与子串的 “f” 不相等,本次匹配失败。下一次匹配,i=6,j=next[j],j=5;主串与子串仍然不等…,直到 j=1 ,还是不等。我们可以发现,j 等于 1,2,3,4,5 的比较过程完成完全是多余的。

1、求子串 “aaaaf” 改进后的 next 数组值
  (1)、标准 KMP 计算出来的 next 数组是 {0 1 2 3 4};
  (2)、当 j = 1时,根据公式,next[1] = 0;
  (3)、当 j = 2时,next 的值为 1,当前字符与第 1 个字符相等,故next[2]=next[1]=0;
  (4)、当 j = 3时,next 的值为 2,当前字符与第 2 个字符相等,故next[3]=next[2]=0;
  (5)、当 j = 4时,next 的值为 3,当前字符与第 3 个字符相等,故next[4]=next[3]=0;
  (6)、当 j = 5时,next 的值为 4,当前字符与第 4个字符不等,故next[5]=4;
  所以改进后的 KMP 算法计算出来的 next 数组为 {0 0 0 0 4}。

2、求子串 “ababaaaba” 改进后的 next 数组
  (1)、标准 KMP 计算出来的 next 数组是 {0 1 1 2 3 4 2 2 3};
  (2)、当 j = 1时,根据公式,next[1] = 0;
  (3)、当 j = 2时,next 的值为 1,当前字符与第 1 个字符不等,故next[2]=1;
  (4)、当 j = 3时,next 的值为 1,当前字符与第 1 个字符相等,故next[3]=next[1]=0;
  (5)、当 j = 4时,next 的值为 2,当前字符与第 2 个字符相等,故next[4]=next[2]=1;
  (6)、当 j = 5时,next 的值为 3,当前字符与第 3个字符相等,故next[5]=next[3]=0;
  (7)、当 j = 6时,next 的值为 4,当前字符与第 4 个字符不等,故next[6]=4;
  (8)、当 j = 7时,next 的值为 2,当前字符与第 2 个字符不等,故next[7]=2;
  (9)、当 j = 8时,next 的值为 2,当前字符与第 2 个字符相等,故next[8]=next[2]=1;
  (10)、当 j = 9时,next 的值为 3,当前字符与第 3个字符相等,故next[9]=next[3]=0;
  所以改进后的 KMP 算法计算出来的 next 数组为 {0 1 0 1 0 4 2 1 0}。

三、程序实现

  注意:经过如下算法计算出来的 next 数值内容比理论计算出来的 next 数值内容小 1 ,这是因为理论计算中,从 1 开始计算,而这种计算方法是从 0 开始计算的,相对来说减少了存储空间,但是不便于理解,客观的说这种算法比较牵强,更细致明白的方法请参考《大话数据结构》。

//改进后的 KMP 模式匹配算法 
//设计思想来自于《大话数据结构》

 
#include <stdio.h> 

#define T_LENGTH 9
#define S_LENGTH 20

void GetNext(char string[], int next[]); 
int GetIndex(char s[], char t[], int next[]);

int main(){
	
	int next[T_LENGTH] = {};
	
	char s[S_LENGTH] = "eababaaabaeeeeeeee";
	char t[T_LENGTH] = "ababaaaba";
	
	GetNext(t, next); 
	printf("%d ",GetIndex(s, t, next));

	
	return 0;
}


//获取 string 子串的标准 KMP 算法下的 next 数组
//next 数组的长度为 T_LENGTH,从 0 开始存储
//string 的长度为 T_LENGTH 
void GetNext(char string[], int next[]){
	
	int i = 0;	// i 位置代表 string 字符串的当前位置下标 
	int j = -1;	// j 表示需要回溯的位置
				// 从 -1 开始是和后面 if 判断保持一致 
	
	next[0] = -1;	//根据公式,首字符的 next 值为 0。但是此处为 -1 为了和后面保持一致	
	
	while(i < T_LENGTH){
		
		
		// j自增一次后才是正确初始位置 0 
		if((j == -1) || (string[i] == string[j])){
			++i;
			++j;
			
			if(string[i] != string[j]){
				next[i] = j; 
			}else{
				next[i] = next[j];
			}
		}else{
			j = next[j];
		}
	} 
	
//	for(i = 0; i < T_LENGTH; ++i){
//		printf("%d  ",next[i]);
//	}printf("\n\n");
	
}

//返回子串 t 在主串 s 中的位置索引,如果没有,则返回 -1 
int GetIndex(char s[], char t[], int next[]){
	
	int i = 0;
	int j = -1;
	
	while(i < S_LENGTH-1 && j < T_LENGTH-1){
		if(j == -1 || s[i] == t[j]){
			++i;
			++j;
		}else{
			j = next[j];
		}
		
	}
	
	printf("j = %d daf\n",j);
	
	if(j >= T_LENGTH -1){
		return i - T_LENGTH + 1;
	}else{
		return -1;
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值