KMP算法的分析及实现(C语言)

一、算法简介

  KMP算法是由D.E.Knuth、J.HMorris和V.R.Pratt(其中Knuth和Pratt共同研究,Morris独立研究)发表的一个模式匹配算法,称为克努特——莫里斯——普拉特算法,简称KMP算法。该算法主要解决字符串匹配时,重复遍历的问题。和传统依次遍历的算法相比,KMP算法更适合于主串与字串之间存在许多部分匹配的情况,否则无法体现出该算法的优势。

二、算法原理

1、算法实现过程简介

  算法中用到一个主字符串和一个子字符串,分别简称为 “主串” 和 “字串”,具体的功能是在主串中找到和子串匹配(相等)的连续的字符串,然后根据不同的需求进行相应的操作。
  普通的字符串匹配时,选定好主串和子串中的起始位置下标,然后依次判断是否主串和子串的字符是否相等,如果出现不相等的字符,则确认本次匹配失败,结束本次匹配;如果一直判断直到最后两个字符相等,则本次匹配成功。如果匹配的结果为失败,则需要重新开始下一次的匹配,此时主串的下标需要退回到上一次的起始下标之后,子串的位置下标退回到子串的起始位置,重复上述匹配过程,直到成功匹配或者遍历完整个主串。
  KMP进行字符串匹配时,首先选定主串和子串的起始位置下标,下标同时依次进行遍历比较,如果一直相等直到子串结束,则本次匹配成功;如果本次匹配失败,则主串的位置下标不用退回,保持在匹配失败的位置,子串的位置下标退回到 next 数组中存储的位置处,开始下一次的匹配,直到整个主串遍历完成或者成功匹配。
  在普通字符串匹配的过程中,每开始一次匹配,主串和子串的下标都需要退回至一定的位置。KMP进行字符串匹配时,每开始一次匹配,主串的当前位置下标不用退回,子串的当前位置下标也不用退回至起始位置,而是退回至 next 数组中的指定位置

2、next 数组

2.1、简介

  nest 数组的长度和子串的长度一致,存放的是子串的当前位置下标需要退回的位置。给定一个字符串 “ababaaaba”,它对应的 next 数组中的值是{0 1 1 2 3 4 2 2 3},假定 next 数组的下标从 1 开始,则 next[7]==2,表示如果出现了主串和子串元素不匹配的情况,子串从 2 的位置重新开始比较即可,不用从 1 的位置开始。

2.2、公式

{ 0 , 当 j = 1 时 M a x { k ∣ 1 < k < j , 且 ′ P 1 . . . P k − 1 ′ = ′ P j − k + 1... P j − 1 ′ } 1 , 其 它 情 况 \left\{ \begin{matrix} 0,当 j = 1时 \\ Max\left\{k|1<k<j,且'P_1...P_k-1'='P_j-k+1...P_j-1' \right\} \\ 1,其它情况 \\ \end{matrix} \right. 0j=1Max{k1<k<j,P1...Pk1=Pjk+1...Pj1}1

2.3、求值过程

  在字符串 “abcabx” 中,next[6] 对应的子串为 “abcab”,其中前缀 “ab” 等于后缀 “ab”,共计两个字符相等,以此类推。根据经验,前后缀一个字符相等,对应的 next 为 2;前后缀 n 个字符相等,对应的 next 值为 n+1。

2.4、求值原理

  在字符串子串 “abcabx” 中,next[6] 的值为3,假设主串为 “abcabedfd”。匹配到主串中的 “e” 和子串中的 “x” 不相等,本次匹配失败。因为本次匹配已经确定了主串中下标为 4、5 的元素和子串中下标为 4、5 的元素相等,下一次匹配时,主串的位置下标从 6 开始,主串元素下标为 6 的元素前两个元素为 “ab”,而子串的前两个元素也为 “ab”,所以子串从下标为 3 的元素起开始遍历。
  在 next 数组的求值过程中,具体的值仅仅取决于子串的内容,和主串无任何关系。

2.5、案例分析

  具体的求值过程可以根据公式去求取,也可以用经验去求取。
2.5.1、abcabx 公式
  设子串的位置下标为 j,next[j] = k
  (1)、j = 1 时,next[1] = 0;
  (2)、j = 2 时,j 由 1 到 j-1 只有字符 “a”,属于其它情况,next[2] = 1;
  (3)、j = 3 时,j 由 1 到 j-1 的串为 “ab”,显然 “a” 不等于 “b”,属于其它情况,next[3] = 1;
  (4)、j = 4 时,j 由 1 到 j-1 的串为 “abc”,前缀字符串与后缀字符串没有相等的可能性,因此 next[4] = 1;
  (5)、j = 5 时,j 由 1 到 j-1 的串为 “abca”,前缀字符串 “a”与后缀字符串 “a” 相等,则 k = 2(j-k+1=4,j=5),所以 next[5] = 2;
  (6)、j = 6 时,j 由 1 到 j-1 的串为 “abcab”,前缀字符串 “ab” 与后缀字符串 “ab” 相等,则 k = 3(j-k+1=4,j = 6),所以 next[6] = 3;
  所以子串 “abcabx” 的 next 数组为 {0 1 1 1 2 3}。

2.5.2、ababaaaba 经验
  设子串的位置下标为 j,next[j] = k
  (1)、j = 1 时,next[1] = 0;
  (2)、j = 2 时,j 由 1 到 j-1 只有字符 “a”,属于其它情况,next[2] = 1;
  (3)、j = 3 时,j 由 1 到 j-1 只有字符 “ab”,前缀字符串与后缀字符串不相等(0个相等)相等,因此next[3] = 1;
  (4)、j = 4 时,j 由 1 到 j-1 只有字符 “aba”,前缀字符串 “a” 与后缀字符串 “a”相等,相等的字符个数为 1,因此next[4] = 2;
  (5)、j = 5 时,j 由 1 到 j-1 只有字符 “abab”,前缀字符串 “ab” 与后缀字符串 “ab”相等,相等的字符个数为 2,因此next[5] = 3;
  (6)、j = 6 时,j 由 1 到 j-1 只有字符 “ababa”,前缀字符串 “aba” 与后缀字符串 “aba”相等,相等的字符个数为 3,因此next[6] = 4;
  (7)、j = 7 时,j 由 1 到 j-1 只有字符 “ababaa”,前缀字符串 “a” 与后缀字符串 “a”相等,相等的字符个数为 1,因此next[7] = 2;
  (8)、j = 8 时,j 由 1 到 j-1 只有字符 “ababaaa”,前缀字符串 “a” 与后缀字符串 “a”相等,相等的字符个数为 1,因此next[8] = 2;
  (9)、j = 9 时,j 由 1 到 j-1 只有字符 “ababaaab”,前缀字符串 “ab” 与后缀字符串 “ab”相等,相等的字符个数为 2,因此next[9] = 3;
  所以子串 “ababaaaba” 的 next 数组为 {0 1 1 2 3 4 2 2 3}。

三、程序实现

//标准的 KMP 算法
//参考《大话数据结构》 

#include <stdio.h> 

void GetNext(char string[], int next[], int length); 
int IndexKMP(char s[], int s_length, char t[], int t_length, int pos);

int main(){
	
	char t_string[10] = " ababaaaba";
	char s_string[13] = " cdababaaaba2"; 
	
	printf("\n%d",IndexKMP(s_string,12+1,t_string,9+1,1));
	
	return 0;
}

//通过计算返回字串 string 的 next 数组 
//《大话数据结构》上的源码,有改动 
//string 数组的第一个数据元素在无意义。在源程序中存储了 string 子串的长度
//next 数组的实际长度为 length+1。在程序中,next数据从下标 1 开始存储 
//length:子串的长度 + 1
void GetNext(char string[], int next[], int length){
	
	int i = 1;	
	
	int j = 0; 
	
	next[1] = 0;	
	
	while(i < length){
		
		
		//string[i]:后缀单个字符
		//string[j]:前缀单个字符 
		if(j == 0 || string[i] == string[j]){	
			++i;
			++j; 
			next[i] = j;
			
		}else{
			
			j = next[j];
		}
		
	}
}


//返回子串 t 在主串 s 中第 pos 个字符之后的位置,若不存在,则返回 0
//根据《大话数据结构》的源码稍有改动
//s[]:主串;且第一个数组元素无效 
//s_length:主串的长度 + 1
//t[]:子串;且第一个数组元素无效 
//t_length:子串的长度 + 1
//pos:主串的起始位置 
int IndexKMP(char s[], int s_length, char t[], int t_length, int pos){
	
	int i = pos;	//主串中当前下标值 
	
	int j = 1;		//子串中当前下标值 
	
	int next[255] = {};	//定义 next 数组 
	
	GetNext(t, next, t_length);		//获取子串的 next 数组 
	
	while(i < s_length && j < t_length){//	i 小于 s 的长度且 j 小于 t 的长度时 
	
		if(j == 0 || s[i] ==t[j]){
			++i;
			++j;
		}else{
			j = next[j];
		}
	} 
	
	if(j > t_length - 1){	//子串的实际长度 
		return i - t_length + 1;
	}else{
		return 0;
	}
}

  下一篇对 KMP 模式匹配算法存在的缺陷进行改进。

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值