数据结构-数组-字符串匹配:Knuth-Morris-Pratt算法(详解附完整代码)

字符串抽象数据类型

  • C++语言中包含一个string类,其ADT中包含很多定义的函数,这里就不再详细赘述。

字符串模式匹配

简单的字符串匹配

  • 检验字符串pat是否在str中最简单但最低效的方法:逐个考虑str内每个位置,判断其是否是匹配的起始地址。
    在这里插入图片描述

  • 代码如下:

// 若匹配返回匹配起始地址,否则返回-1
int Find(const string &str, const string &pat)
{
	//获取字符串长度
	int lengthS = str.size();
	int lengthP = pat.size();

	for (int s = 0; s < lengthS; s++)
	{
		int j;
		for (j = 0; j < lengthP && str[s + j] == pat[j]; j++);
		//匹配到结果返回起始地址
		if (j == lengthP)return s;
	}
	//非匹配到结果
	return -1;
}

Knuth-Morris-Pratt算法

背景分析

  • 上面提到的简单算法的时间复杂度为 O ( l e n g t h S × l e n g t h P ) O(lengthS\times lengthP) O(lengthS×lengthP),能否有一种算法将时间复杂度控制在 O ( l e n g t h S + l e n g t h P ) O(lengthS+lengthP) O(lengthS+lengthP)
  • 为了实现这一想法,我们希望能无需回溯地在字符串中,即当发生一个不匹配(失配)的情况时,我们希望利用模式串(Pat)中不匹配字母和失配位置等相关信息确定继续搜索的位置。如图所示:
    在这里插入图片描述
  • 为了确定模式串的适配信息,我们给模式串定义一个失配函数

失配函数

定义
  • p = p 0 p 1 . . . p n − 1 p=p_0p_1...p_{n-1} p=p0p1...pn1是一个模式串,定义它的失配函数(failure function), f f f为:

f ( j ) = { k , k 为 满 足 p 0 . . . p k = p j − k . . . p j 且 k < j 的 最 大 整 数 , k 存 在 且 k ≥ 0 − 1 , 无 满 足 k f(j) = \begin{cases} k,&k为满足p_0...p_k=p_{j-k}...p_j且k<j的最大整数,k存在且k\ge 0\\ -1, & 无满足k \end{cases} f(j)={k,1,kp0...pk=pjk...pjk<jkk0k

  • 例如模式串 p a t = a b c a b c a c a b pat=abcabcacab pat=abcabcacab,有:
j0123456789
patabcabcacab
f-1-1-10123-101
实现方法
  • 基于失配函数的另一种表达形式 :

f ( j ) = { − 1 , j = 0 f m ( j − 1 ) + 1 , m 是 满 足 p f k ( j − 1 ) + 1 = p j 的 整 数 k 的 最 小 值 − 1 , 其 他 情 况 f(j)= \begin{cases} -1,&j=0\\ f^m(j-1)+1,&m是满足p_{f^k(j-1)+1}=p_j的整数k的最小值\\ -1,&其他情况 \end{cases} f(j)=1,fm(j1)+1,1,j=0mpfk(j1)+1=pjk
注意 f 1 ( j ) = f ( j ) , f m ( j ) = f ( f m − 1 ( j ) ) f^1(j)=f(j),f^m(j)=f(f^{m-1}(j)) f1(j)=f(j),fm(j)=f(fm1(j))

  • 代码如下:
//失配函数
int* FailureFunction(const string &pat)
{
	int lengthP = pat.size();
	int* f = new int[lengthP];
	int j = 0, k = -1;

	f[0] = -1;
	while (j < lengthP - 1)
	{
		if (pat[j + 1] == pat[k + 1])
		{
			j++;
			k++;
			f[j] = k;
		}
		else if (k == -1)
		{
			j++;
			f[j] = -1;
		}
		else k = f[k];
	}
	//返回f[lengthP]指针
	return f;
}
函数分析
  • 可知失配函数的时间复杂度为 O ( l e n g t h P ) O(lengthP) O(lengthP).

KMP函数

实现方法
  • 字符串(str的指针索引为 P o s S PosS PosS模式串(pat的指针索引为 P o s P PosP PosP
  • 在匹配失败回溯时, P o s S PosS PosS不变 P o s P = f [ P o s P − 1 ] + 1 ( P o s P ≠ 0 ) PosP=f[PosP-1]+1(PosP\ne0) PosP=f[PosP1]+1(PosP=0).
    代码如下:
int KMP(const string& str, const string& pat)
{
	int LengthP = pat.size(), LengthS = str.size();
	int* f;

	//生成失配函数
	f = FailureFunction(pat);

	//查询过程
	int PosP = 0, PosS = 0;
	while (PosP < LengthP && PosS < LengthS)
	{
		if (pat[PosP] == str[PosS])
		{
			PosP++;
			PosS++;
		}
		else
		{
			if (PosP == 0)
				PosS++;
			else PosP = f[PosP - 1] + 1;
		}
	}
	//匹配到结果返回起始地址;
	//非匹配到结果返回-1
	return PosP < LengthP || LengthP == 0 ? -1 : PosS - PosP;
}
函数分析
  • 该算法的时间复杂度为 O ( l e n g t h S + l e n g t h P ) O(lengthS+lengthP) O(lengthS+lengthP).

失配信息的另一种方法:next函数

定义

n e x t [ j ] = { − 1 , j = 0 n e x t m ( j ) + 1 , m 是 满 足 p n e x t k ( j ) = p j 的 整 数 k 的 最 小 值 0 , n e x t m ( j ) = − 1 next[j]= \begin{cases} -1,&j=0\\ next^m(j)+1,&m是满足p_{next^k(j)}=p_j的整数k的最小值\\ 0,&next^m(j)=-1 \end{cases} next[j]=1,nextm(j)+1,0,j=0mpnextk(j)=pjknextm(j)=1

实现方法
//失配函数2
int* Next(const string & pat)
{
	int lengthP = pat.size();
	int* next = new int[lengthP];
	int j = 0, k = -1;

	next[0] = -1;
	while (j < lengthP)
	{
		if (k == -1 || pat[j] == pat[k])
		{
			j++;
			k++;
			next[j] = k;
		}
		else k = next[k];
	}

	return next;
}
  • 此时KMP算法如下:
int KMP(const string& str, const string& pat)
{
	int LengthP = pat.size(), LengthS = str.size();
	int* f;
	
	//生成失配函数2
	f = Next(pat);

	//查询过程
	int PosP = 0, PosS = 0;
	while (PosP < LengthP && PosS < LengthS)
	{
		if (pat[PosP] == str[PosS])
		{
			PosP++;
			PosS++;
		}
		else
		{
			if (PosP == 0)
				PosS++;
			else PosP = next[PosP];
		}
	}
	//匹配到结果返回起始地址;
	//非匹配到结果返回-1
	return PosP < LengthP || LengthP == 0 ? -1 : PosS - PosP;
}

代码总览

#include<iostream>
using namespace std;

// 若匹配返回匹配起始地址,否则返回-1
int Find(const string &str, const string &pat)
{
	//获取字符串长度
	int lengthS = str.size();
	int lengthP = pat.size();

	for (int s = 0; s < lengthS; s++)
	{
		int j;
		for (j = 0; j < lengthP && str[s + j] == pat[j]; j++);
		//匹配到结果返回起始地址
		if (j == lengthP)return s;
	}
	//非匹配到结果
	return -1;
}


//失配函数1
int* FailureFunction(const string &pat)
{
	int lengthP = pat.size();
	int* f = new int[lengthP];
	int j = 0, k = -1;

	f[0] = -1;
	while (j < lengthP - 1)
	{
		if (pat[j + 1] == pat[k + 1])
		{
			j++;
			k++;
			f[j] = k;
		}
		else if (k == -1)
		{
			j++;
			f[j] = -1;
		}
		else k = f[k];
	}
	//返回f[lengthP]指针
	return f;
}

//失配函数2
int* Next(const string & pat)
{
	int lengthP = pat.size();
	int* next = new int[lengthP];
	int j = 0, k = -1;

	next[0] = -1;
	while (j < lengthP)
	{
		if (k == -1 || pat[j] == pat[k])
		{
			j++;
			k++;
			next[j] = k;
		}
		else k = next[k];
	}

	return next;
}

int KMP(const string& str, const string& pat)
{
	int LengthP = pat.size(), LengthS = str.size();
	int* f;

	//生成失配函数1
	f = FailureFunction(pat);

	//生成失配函数2
	//f = Next(pat);

	//查询过程
	int PosP = 0, PosS = 0;
	while (PosP < LengthP && PosS < LengthS)
	{
		if (pat[PosP] == str[PosS])
		{
			PosP++;
			PosS++;
		}
		else
		{
			if (PosP == 0)
				PosS++;
			else PosP = f[PosP - 1] + 1;//PosP = next[PosP];
		}
	}
	//匹配到结果返回起始地址;
	//非匹配到结果返回-1
	return PosP < LengthP || LengthP == 0 ? -1 : PosS - PosP;
}

int main()
{
	string str = "abcasdababcabcacbddfhs", pat = "abcabcacbd";
	cout << KMP(str, pat) << endl;
}

上一篇:数据结构-数组-稀疏矩阵表示与多维矩阵(转置、加法、乘法,附完整代码)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一棵___大树

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值