字符串匹配算法2:KMP算法

字符串匹配算法1:暴力匹配算法
https://blog.csdn.net/lqy971966/article/details/99770472

字符串匹配算法2:KMP算法
https://blog.csdn.net/lqy971966/article/details/105712559

字符串匹配算法3:Boyer-Moore 算法
https://blog.csdn.net/lqy971966/article/details/106026651

1. 背景:

KMP 算法是 D.E.Knuth、J,H,Morris 和 V.R.Pratt 三位神人共同提出的,称之为 Knuth-Morria-Pratt 算法,简称 KMP 算法。

  1. 根据暴力方法的缺点,而引出KMP算法的思想。
  2. 在暴力匹配方法中,上一次匹配失败,则下一次匹配开始时,主串指针会回溯到i+1,模式串指针会回退到0。
    结果: 如此暴力方法未能利用已经匹配上的字符的信息,造成了重复匹配。
    参考上一篇暴力匹配的博客: https://blog.csdn.net/lqy971966/article/details/99770472

2. KMP算法思想

2.1 KMP思想

KMP算法思想便是利用已经匹配上的字符信息,使得模式串的指针回退的字符位置能将主串与模式串已经匹配上的字符结构重新对齐。

2.2 匹配位置处理

KMP算法中每一次的匹配

  1. 主串的起始位置 = 上一轮匹配的失配位置;
  2. 模式串的起始位置 = 重复字符结构的下一位字符(无重复字符结构,则模式串的首字符)

2.3 例子

模式串P="abcac"匹配主串T="ababcabcacbab"的KMP过程如下图:
在这里插入图片描述

  1. 当a与c不匹配时,前面ab的next函数为0 0 ,由公式算出向后移动的位数
    移动位数 = 已匹配的字符数 - 对应的部分匹配值=2-0=2
    所以模式串往前移动2位
  2. 当第二次,b与c不匹配时,abca的next函数为:0 0 0 1
    移动位数=4-1=3
    参考: http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html

3. 失配函数

3.1 什么是失配函数?

  1. 失配函数:
    表示模式串P[0…j]的前缀与后缀完全匹配的最大长度
    也表示了模式串中重复字符结构信息
  2. 部分匹配函数(Partial Match,在数据结构书[2]称之为失配函数):
    在这里插入图片描述

3.2 next函数

next[j]=f(j)+1
就是next函数中的每个值等于失配函数的值+1
next[j]函数表示对于模式串失配位置j+1,下一轮匹配时模式串的起始位置(即对齐于主串的失配位置)

  1. next数组本质
    next数组就是求前面串中前后缀相等的最大长度

3.3 next数组计算过程

  1. 首先next[0]=-1,next[1]=0;
  2. 之后每一位j的next求解:
    比较j-1字符与next[j-1]是否相等,
    2.1 如果相等则next[j]=next[j-1]+1,
    如果不相等,则比较j-1字符与next[next[j-1]]是否相等,
    如果相等则next[next[j-1]]+1,
    如果不相等则继续以此下去,直到next[…]=-1,则next[j]=0.

通俗易懂的话来说就是你要求解当前位的next值,则看前一位与前一位next所在位字符是否相等,相等则当前位的next等于前一位next+1,如果不等就找前一位next的next与前一位比较,如果相等,当前位的next等于那个与前一位字符相等的字符所在位置+1,如果还是不相等,则以此类推,直到出现比较位为next[]=-1,那当前位的next就等于-1。

3.3 例子

模式串P="ababababca"的部分匹配函数与next函数如下:
在这里插入图片描述

3.4 (模式串P)next代码

假设模式串
P = "    a b a b a b b b a b"
next[]= -1 0 0 1 2 3 4 0 0 1   
 
void getNext(char *p, int *next)
{
	int len = strlen(p); //模式串的长度
	int k = -1; //记录当前位的next
	int j = 0; //当前位下标
	next[0] = -1; //默认 next[0] = -1 next[1]=0
	while(j < len-1) //求解完所有字符的next
	{
		//比较j-1字符与next[j-1]是否相等
		if(k == -1 || p[k] == p[j])  //如果相等则next[j]=next[j-1]+1
		{
			k++; //当前位的next值+1作为下一位的next值
			j++;
			next[j] = k; //求解出下一位的next值
		}
		else{ 
			/*
			如果不相等,则比较j-1字符与next[next[j-1]]是否相等
			如果还是不相等,则以此类推,
			直到出现比较位为next[]=-1,那当前位的next就等于-1
			*/
			k = next[k]; //如果不相等则找当前位的next的next与当前位比较
		}
	}
	
	printf("\n next[]= ");
	for(int i =0 ;i< len; i++){
		printf("%d   ", next[i]);
	}
}

4. KMP算法实现

如: 
主串   s[] = ababababbbab
模式串 p[] = abababbbab

int kmp(char *s, char *p, int *next)
{
	int i = 0;
	int j = 0;
	int sLen = strlen(s);
	int pLen = strlen(p);
	
	while(i < sLen && j < pLen) 
	{
		if (j == -1 || s[i] == p[j]) //next[j] = -1 或者 字符匹配 则 s p都往后移动
		{
			i++;
			j++;
		}
		else
		{
			//j回退。。。  
			//如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j] 
			//next[j]即为j所对应的next值      
			j = next[j]; 
		}
	}
	
	if(j >= pLen)
	{
		return(i-j); //匹配成功,返回子串的位置
	}
	
	return -1; //没匹配到
} 

5. 代码例子

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define ARR_LENGTH 512

void getNext(char *p, int *next)
{
	int len = strlen(p); //模式串的长度
	int k = -1; //记录当前位的next
	int j = 0; //当前位下标
	next[0] = -1; //默认 next[0] = -1 next[1]=0
	while(j < len-1) //求解完所有字符的next
	{
		//比较j-1字符与next[j-1]是否相等
		if(k == -1 || p[k] == p[j])  //如果相等则next[j]=next[j-1]+1
		{
			k++; //当前位的next值+1作为下一位的next值
			j++;
			next[j] = k; //求解出下一位的next值
		}
		else{ 
			/*
			如果不相等,则比较j-1字符与next[next[j-1]]是否相等
			如果还是不相等,则以此类推,
			直到出现比较位为next[]=-1,那当前位的next就等于-1
			*/
			k = next[k]; //如果不相等则找当前位的next的next与当前位比较
		}
	}
	
	printf("\n next[]= ");
	for(int i =0 ;i< len; i++){
		printf("%d   ", next[i]);
	}
}

int kmp(char *s, char *p, int *next)
{
	int i = 0;
	int j = 0;
	int sLen = strlen(s);
	int pLen = strlen(p);
	
	while(i < sLen && j < pLen) 
	{
		if (j == -1 || s[i] == p[j]) //next[j] = -1 或者 字符匹配 则 s p都往后移动
		{
			i++;
			j++;
		}
		else
		{
			//j回退。。。  
			//如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j] 
			//next[j]即为j所对应的next值      
			j = next[j]; 
		}
	}
	
	if(j >= pLen)
	{
		return(i-j); //匹配成功,返回子串的位置
	}
	
	return -1; //没匹配到

} 

int main(){
	int next[ARR_LENGTH] = 0;
	int iLoc = 0;
	//char s[ARR_LENGTH] = "ababcabcacbab";
	//char p[ARR_LENGTH] = "abcac";
	char s[ARR_LENGTH] = "ababababbbab";
	char p[ARR_LENGTH] = "abababbbab";
	printf("START:");
	printf("\n s[] = %s", s);
	printf("\n p[] = %s", p);
	getNext(p,next);
	iLoc=kmp(s,p,next);
	printf("\n匹配的位置: %d \n",iLoc);
	printf("END:");
	return 0;
	
} 

结果:

START:
s[] = ababababbbab
p[] = abababbbab
next[]= -1   0   0   1   2   3   4   0   0   1   
匹配的位置: 2 
END:

6. 参考

https://mp.weixin.qq.com/s/dHI_U1vAIS4C5NP-vpzyJw
https://www.cnblogs.com/en-heng/p/5091365.html
https://baijiahao.baidu.com/s?id=1659735837100760934&wfr=spider&for=pc
http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html
https://blog.csdn.net/weixin_43904021/article/details/87792889

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值