数据结构之字符串的模式匹配(朴素算法+KMP)

数据结构之字符串的模式匹配(朴素算法+KMP)

一、引言

子串的定位操作通常称为串的模式匹配,他求的是子串(常称模式串)在主串中的位置,这里采用定长顺序存储结构,给出一种不依赖于其他操作的暴力匹配算法

数据结构定义

首先,我们定义一个结构体 SString 来表示串,其中包含字符数组 ch 和整型变量 length 表示串的长度。

#define MAXLEN 255

typedef struct {
    char ch[MAXLEN + 1];  // 用于存储字符的数组
    int length;           // 串的实际长度
} SString;
子串与模式串的区别
  • 子串
    是主串的一部分,他是一定存在的
  • 模式串
    不一定能在主串中找到

二、朴素算法(简单的模式匹配算法)

int Index(SString S,SString T){
	int i=1,j=1;
	while(i<=S.length && j<=T.length){
		if(S.ch[i]==T.length){
			++i,++j;//继续比较后继字符 
		}
		else{
			i=i-j+2;
			j=1;	//指针后退重新开始匹配 
	}
	if(j>T.length)
		return i-T.length;
	return 0;
	}
}

注意点

  • 使用此算法匹配失败时,主串指针i会 疯狂回溯
  • 若主串有m个,模式串有n个,最多对比 n-m+1 个子串
  • 该算法的最坏时间复杂度为 O(m+n)

三、KMP算法

KMP算法(Knuth-Morris-Pratt算法)是一种用于在文本串(主串)中查找子串(模式串)的高效算法。它通过利用已经部分匹配的信息来减少不必要的字符比较次数,从而提高查找效率。由三个伟人提出,各取一个字母命名

原理概述

KMP算法的关键在于构建一个next数组,这个数组用于指示在匹配失败时,模式串应该向右移动多少位。其核心思想是利用模式串自身的特性,减少主串和模式串的重复比较。

next数组的构建

next数组是根据模式串自身的前缀和后缀的最长公共元素长度来构建的。具体步骤如下:

  1. 初始化next[1] = 0,表示模式串只有一个字符时,无需移动。
  2. 遍历构建:从模式串的第二个字符开始计算,依次填充next数组。
    • 如果当前字符匹配失败(即主串和模式串当前比较的字符不相等),则根据next数组回退模式串的位置。
    • next[j]的值表示当模式串的第j个字符匹配失败时,模式串应该向右移动的位置。
    • 如果T.ch[j]T.ch[next[j]]相等,则next[j+1] = next[j] + 1;否则,继续向前回溯,直到找到一个匹配或者无法再回溯为止。

算法实现

以下是一个简化的KMP算法实现,用于在主串S中查找模式串T的起始位置:

int Index(SString S,SString T,int next[]){
	int i=1,j=1;
	while(i<=S.length && j<=T.length){
		if(j==0 || S.ch[i]==T.ch[j]){
			++i;
			++j;	//继续比较后继字符 
		}
		else{
			j=next[j];	//模式串向右移动 
	}
	if(j>T.length)
		return i-T.length;
	return 0;
	}
}

应用及效率

KMP算法适用于需要频繁查找多个模式串的情况,如文本编辑器中的查找功能。它的时间复杂度为O(m + n),其中m为主串长度,n为模式串长度,具有较高的查找效率。

注意点

  • KMP算法利用部分匹配的信息,避免在主串中回溯过多次数,从而提高了字符串匹配的效率。它的核心在于构建next数组,通过这一数组来指导模式串的移动,使得匹配过程更加智能和高效。
  • next 数组与主串无关,及只知道模式串的元素,即可求出
  • 时间复杂度为 O(m+n) ,其中,球next数组的时间复杂度 O(m) , 模式匹配过程中的最坏时间复杂度为 O(n)
  • 简化过程是:根据模式串T,求出 next 数组,在利用 next 数组进行匹配(主串指针不回溯)
  • next 数组的快速求法
  1. next[1] , next[2] 无脑为0
  2. 其他next,在不匹配的位置前,划一道优美的分界线,模式串一步一步往后退,直到分界线之前能对上,或者模式串完全跨过分界线,此时 j 指向哪里,next数组的值就是多少

五、KMP算法的进一步优化

通过主串与字符串的比较,我们可以知道在主串与字符串不相同的位置之前的元素是多少,由此,变通过构建 next 数组,来让模式串后移,提高效率。
除此之外,通过主串与字符串的比较,我们也可以知道,在不相同的位置,主串的元素一定不是模式串该位置的元素,由此,便可以多后移几位,来搭建 nextval 数组,来进一步提高效率
我们可以通过以下这个方法,来快速求得 nextval 数组

nextval[1]=0;
	for(int j=2; j<T.length; j++) {
		if(T.ch[next[j]]==T.ch[j])
			nextval[j]=nextval[next[j]];
		else
			nextval[j]=next[j];
	}
  • 优化思路
    构建next 数组的同时,根据模式串当前位置的字符是否与 next 数组所指示的下一个字符相等,决定是否继承上一次的nextval 值。

六、总结

KMP算法通过 next 数组的 预处理 ,实现了在字符串匹配过程中的高效移动模式串的能力,而 nextval 数组的优化进一步提升了算法的效率。通过合理的优化,KMP算法在实际应用中能够显著减少比较次数,提高匹配效率。
学习KMP算法时,应从暴力法的弊端入手,思考如何去优化它,实际上,已匹配相等的序列就是模式串的某个前缀,因此每次回溯就相当于模式串与模式的某个前缀比较,这种频繁的重复比较适合效率低的原因。这时,可以从分析模式串本身的结构入手,以便得知当模式匹配到某个字符不相同时,应该往后滑动到什么位置,即已匹配相等的前缀和模式串若收尾重合,则对齐他们,对齐的部分显然无需再比较,下一步则是直接从主串的当前位置继续进行比较

  • 28
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值