KMP匹配算法

字串的定位操作通常称为串的的匹配模式

朴素的模式匹配算法

假设我们要匹配下面的主串S=“goodgoogle”,中,找到T="google"这个字串位置,我们按朴素模式匹配算法需要下面几步
1.从主串第一个位置开始,S与T的前三个都匹配成功,第四个匹配失败

S:g o o d g o o g l e
  | | | !
T:g o o g l e

2.从主串第二个位置开始,重新开始匹配

S:g o o d g o o g l e
    !  
T:  g o o g l e

3.继续上面过程

S:  g o o d g o o g l e
        !  
T:      g o o g l e

4.匹配失败,继续匹配

S:  g o o d g o o g l e
          !  
T:        g o o g g l e

5.匹配失败,继续匹配

S: g o o d g o o g l e
         !  
T:       g o o g l e

6.匹配成功。

S:g o o d g o o g l e
          | | | | | |
T:        g o o g l e

上面就是最普通的匹配算法
预备工作

#define Max 1000
typedef char String[Max];

代码如下
都是从下标为零的位置开始的

//朴素的模式匹配算法
int index(String S,String T,int pos)
{
	int i = pos-1;
	int j = 0;
	int len1 = strlen(S);
	int len2 = strlen(T);
	while (i < len1 && j <len2) 
	{
		if (S[i] == T[j])
		{
			++j;
			++i;
		}
		else
		{
			i = i - j + 1;     //匹配失败,回溯到首次匹配位置的下一位
			j = 0;
		}
	}
	if (j >= len2)           //若匹配成功,则返回匹配到的位置下标
		return i - len2;  
	else
		return 0;           //若匹配失败,则返回 0
}

KMP模式匹配算法

朴素的模式匹配算法的匹配算法虽然很容易理解,但效率极其低下,特别是当出现部分匹配的情况时,需要不断的回溯
所以聪明的前辈们想出了KMP模式匹配算法

我们先看看下面递推过程
S=“abcdeabcdef”,T=“abcdef”
进行匹配

S:a b c d e a b c d e f
  | | | | | !
T:a b c d e f

在匹配到 f 时匹配失败,按朴素模式匹配算法的话,下一步,因该又从第二位开始匹配
但我们知道
T(1) !=T(2) !=T(3) !=T(4) !=T(5) !=T(6)
并且
T(1)=S(1),T(2)=S(2),T(3)=S(3),T(4)=S(4)
可以推出,在S(6)之前是没有可以和T串匹配的字符串的,所以可以直接跳到S(6)位置开始匹配
如下:

S:a b c d e a b c d e f
            | | | | | |
T:          a b c d e f

匹配成功
再看下面例子:
S =“abcababca”,T=“abcabx”
先从第一个位置开始匹配

S:a b c a b a b c a b x
  | | | | | !    
T:a b c a b x

T(1)!=T(2)!=T(3)
因为
T(1)=S(1),T(2)=S(2),T(3)=S(3),T(4)=S(4),T(5)=S(5)

T(1)=T(4),T(2)=T(5)
所以我们只能保证,S(4)前的是没有和T串匹配的
所以这次匹配失败后
下一次可以直接跳到S(4)位置开始匹配
如下:

S:a b c a b a b c a b x
        | | !   
T:      a b c a b x

继续采用上面方法,下一次可以直接从S(6)位置开始匹配

S:a b c a b a b c a b x
            | | | | | |
T:          a b c a b x

匹配成功

通过上面方法进行字符串匹配的效率明显高于朴素模式匹配算法

next数组的推导

通过规律我们发现,匹配的关键是T串的相似度,在匹配前我们需要对T串进行研究
所以引出了next数组,来存放当前字符前的串的前后缀相似度

1.前缀: 包含首位字母但不包含末位字符的子串

2.后缀: 包含末位字符但不包含首位字符的子串

3.next数组的定义:当主串与模式串的某一位字符不匹配时,模式串要回退的位置

4.next [j]:其值 = 第j位字符前面 j-1位组成的子串的前后缀重合字符数 + 1

eg1:

j1 2 3 4 5 6 7 8 9
模式串Ta b a b a a a b a
next [ j ]0 1 1 2 3 4 2 2 3

当 j=1 时,next[1]=0;
当 j=2 时, 前面就是有只有一个字符 a 所以,next[2] = 1;
当 j=3 时, 前面子串中没有重合前后缀 所以,next[3] = 1;
当 j=4 时,前面字串中 a 重合,所以next[4]=2;
当 j=5 时,前面字串中 ab 重合,所以next[5]=3;
当 j=6 时,前面字串中 aba 重合,所以next[6]=4;
当 j=7 时,前面字串中 a 重合,所以next[7]=2;
当 j=8 时,前面字串中 a 重合,所以next[8]=2;
当 j=9 时,前面字串中 ab 重合,所以next[9]=3;

eg2:

j1 2 3 4 5 6 7 8 9
模式串Ta a a a a a a a b
next [ j ]0 1 2 3 4 5 6 7 8

当 j=1 时,next[1]=0;
当 j=2 时, 前面就是有只有一个字符 a 所以,next[2] = 1;
当 j=3 时, 前面字串中 a 重合,所以next[3]=2;
当 j=4 时,前面字串中 aa重合,所以next[4]=3;
当 j=5 时,前面字串中 aaa重合,所以next[5]=4;
当 j=6 时,前面字串中 aaaa重合,所以next[6]=5;
当 j=7 时,前面字串中 aaaaa重合,所以next[7]=6;
当 j=8 时,前面字串中 aaaaaa重合,所以next[8]=7;
当 j=9 时,前面字串中 aaaaaaa重合,所以next[9]=8;

代码:
都是从下标为零的位置开始的(上面推导过程是从一开始的)

void get_next(String T, int* next)
{
	int i, k;
	i = 1, k = 0;
	next[0] = 0;
	next[1] = 0;
	int len = strlen(T);
	while (i < len-1)
	{
		if (k == 0 || T[i] == T[k])
		{
			next[++i] = ++k;
		}
		else
		{
			k = next[k];
		}
	}
}

KMP算法代码

都是从下标为零的位置开始的

int index_KMP(String S, String T, int pos)
{
	int i = pos;
	int j = 0;
	int next[Max];
	get_next(T, next);
	int len1 = strlen(S);
	int len2 = strlen(T);
	while (i < len1 && j < len2)
	{
		if (j == 0 || S[i] == T[j])
		{
			++i;
			++j;
		}
		else
		{
			j = next[j];
		}
	}
	if (j >= len2)
		return i - len2;
	else
		return 0;
}

KMP模式匹配算法的改进

上面所讲的KMP算法只有在出现模式串与主串之间存在许多“部分匹配”的情况下,才能体现出他的优势
如下面
S =“aaabcde”,T =“aaaaax”
1.第一次匹配

S:a a a a b c d e q
  | | | | !
T:a a a a a x

2.第二次匹配

S:a a a a b c d e q
    | | | !
T:  a a a a a x

3.第三次匹配

S:a a a a b c d e q
      | | !
T:    a a a a a x

4.第四次匹配

S:a a a a b c d e q
        | !
T:      a a a a a x

5.第五次匹配

S:a a a a b c d e q
          !
T:        a a a a a x

匹配失败
由这个例子可看出,并没有的到什么优化
而模式串前面几个字符都是a,那么2,3,4,5那四次匹配显得非常多余,没有必要进行
我们直接可以拿首位的next [1]的值去代替与它相等的字符对应的后续next数组值

nextval数组的推导

j1 2 3 4 5 6 7 8 9
模式串Ta a a a a a a a b
next [ j ]0 1 2 3 4 5 6 7 8
nextval [ j ]0 0 0 0 0 0 0 0 8

代码:

void get_nextval(String T, int* nextval)
{
	int i, k;
	i = 1, k = 0;
	nextval[0] = 0;
	nextval[1] = 0;
	int len = strlen(T);
	while (i < len-1)
	{
		if (k == 0 || T[i] == T[k])
		{
			i++;
			k++;
			if (T[i] != T[k])
			{
				nextval[i]=k;
			}
			else
			{
				nextval[i] = nextval[k];
			}
		}
		else
		{
			k = nextval[k];
		}
	}
}
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

binary~

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

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

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

打赏作者

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

抵扣说明:

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

余额充值