数据结构实现—字符串模式匹配(KMP算法,CC++)

本文介绍KMP算法在字符串模式匹配中的应用,通过实例演示如何利用KMP算法优化匹配过程,减少回溯,提高效率。文章详细解释了next数组的含义及其在KMP算法中的作用,并给出C/C++实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

数据结构实现—字符串模式匹配(KMP算法,C/C++)

  1. ​ 实现字符串的数组存储。
  2. ​ 实现串的朴素的匹配算法,找出子串位置
  3. ​ 编写改进型的串的匹配算法,指出改进在何处,时间复杂度和空间复杂度各是多少。

需求分析

朴素算法会造成大量的回溯匹配,从而运行效率会很低,我们的需求就是减少不必要的回溯从而使得效率进一步提升。程序中的sample函数实现的是字符串的朴素模式匹配,get_next函数的作用是得到每次匹配失败后子串的最佳回溯位置,KMP实现字符串的模式匹配

概要说明

字符串匹配优化采用了KMP算法,先举个例子

char *str = “bacbababadababacambabacaddababacasdsd”;

char *ptr = “ababaca”;12

str有两处包含ptr 分别在str的下标10,26处包含ptr。

一般匹配字符串时,我们从目标字符串str(假设长度为n)的第一个下标选取和ptr长度(长度为m)一样的子字符串进行比较,如果一样,就返回开始处的下标值,不一样,选取str下一个下标,同样选取长度为n的字符串进行比较,直到str的末尾(实际比较时,下标移动到n-m)。这样的时间复杂度是O(n*m)。

KMP算法:可以实现复杂度为O(m+n)

如何简化了时间复杂度: 充分利用了目标字符串ptr的性质(比如里面部分字符串的重复性,即使不存在重复字段,在比较时,实现最大的移动量)。

详细设计

依旧是上面的例子

看目标字符串ptr: ababaca 这里我们要计算一个长度为m的转移函数next。

next数组的含义就是一个固定字符串的最长前缀和最长后缀相同的长度。

比如:abcjkdabc,那么这个数组的最长前缀和最长后缀相同必然是abc。 cbcbc,最长前缀和最长后缀相同是cbc。 abcbc,最长前缀和最长后缀相同是不存在的。

注意最长前缀:是说以第一个字符开始,但是不包含最后一个字符。 比如aaaa相同的最长前缀和最长后缀是aaa。 对于目标字符串ptr,ababaca,长度是7,所以next[0],next[1],next[2],next[3],next[4],next[5],next[6]分别计算的是 a,ab,aba,abab,ababa,ababac,ababaca的相同的最长前缀和最长后缀的长度。由于a,ab,aba,abab,ababa,ababac,ababaca的相同的最长前缀和最长后缀是“”,“”,“a”,“ab”,“aba”,“”,“a”,所以next数组的值是[-1,-1,0,1,2,-1,0],这里-1表示不存在,0表示存在长度为1,2表示存在长度为3。这是为了和代码相对应。

接下来的流程就是一个循环过程了。事实上,每一个字符前的字符串都有最长相等前后缀,而且最长相等前后缀的长度是我们移位的关键,所以我们单独用一个next数组存储子串的最长相等前后缀的长度。而且next数组的数值只与子串本身有关。 总结一下,next数组作用有两个: 一是

next[i]的值表示下标为i的字符前的字符串最长相等前后缀的长度。

二是:

表示该处字符不匹配时应该回溯到的字符的下标

next有这两个作用的源头是:字符串的最长相等前后缀

分析一下KMP算法的时间复杂度: KMP算法中多了一个求数组的过程,多消耗了一点点空间。我们设主串s长度为n,子串t的长度为m。求next数组时时间复杂度为O(m),因后面匹配中主串不回溯,比较次数可记为n,所以KMP算法的总时间复杂度为O(m+n),空间复杂度记为O(m)。相比于朴素的模式匹配时间复杂度O(m*n),KMP算法提速是非常大的。但是我们还可以进一步优化。

看一个例子: 主串s=“aaaaabaaaaac” 子串t=“aaaaac” 这个例子中当‘b’与‘c’不匹配时应该‘b’与’c’前一位的‘a’比,这显然是不匹配的。'c’前的’a’回溯后的字符依然是‘a’。 我们知道没有必要再将‘b’与‘a’比对了,因为回溯后的字符和原字符是相同的,原字符不匹配,回溯后的字符自然不可能匹配。但是KMP算法中依然会将‘b’与回溯到的‘a’进行比对。这就是我们可以改进的地方了。我们改进后的next数组命名为:nextval数组。KMP算法的改进可以简述为: 如果a位字符与它next值指向的b位字符相等,则该a位的nextval就指向b位的nextval值,如果不等,则该a位的nextval值就是它自己a位的next值。

代码

#include<iostream>
#include<cstring>
#include<string>
using namespace std;
int sample(char* str, char* x)
{
   int len1 = strlen(str), len2 = strlen(x);
   for (int i = 0; i < len1 - len2; i++)
   	for (int j = 0; j < len2; j++)
   		if (str[i + j] == x[j])
   		{
   			if (j == len2 - 1)
   				return i;
   		}
   		else
   			break;
   return -1;
}
void get_next(int* next, char* x)
{
   int len = strlen(x);
   int i = 0, j = -1;
   next[0] = -1;
   while (i < len - 1)//防止溢出
   {
   	if (j == -1 || x[i] == x[j])
   	{
   		i++;
   		j++;
   		if (x[i] != x[j])
   			next[i] = j;
   		else
   			next[i] = next[j];
   	}
   	else
   		j = next[j];
   }
}
int KMP(char* str, char* x)
{
   int next[1000];
   int len1 = strlen(str), len2 = strlen(x), i = 0, j = 0;
   get_next(next, x);
   while (i < len1 && j < len2)
   {
   	if (j == -1 || str[i] == x[j])
   	{
   		i++;
   		j++;
   	}
   	else
   		j = next[j];
   	if (j == len2)
   	{
   		return i-len2;
   	}
   }
   return -1;
}
int main()
{
   char str[1000], x[1000];
   scanf("%s%s", str, x);
   cout << "朴素字符串匹配:";
   cout << sample(str, x)+1 << endl;
   cout << "改进后的KMP字符串匹配:";
   cout << KMP(str, x)+1 << endl;
   return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值