首页 > 编程语言 >字符串匹配算法KMP

字符串匹配算法KMP

时间:2023-04-14 13:46:44浏览次数:51  
标签:abac 匹配 pattern bcabasgfd 样板 算法 123456789 KMP 字符串

KMP算法是字符串的匹配算法,比如我给一个名为《文本》的字符串,和一个名为《样板》的字符串,询问《样板》在《文本》中出现过的次数,这就需要字符串匹配算法。对于匹配,形象一点可以看例子:

《文本1》="abcdefghigklmn"
《样板1》="abc"
=============================
《文本2》="abcdefghigklmn"
《样板2》="abcabcabcabcabcabc"

这两个例子,显然例1才是正确的,因为如果样板的长度大于文本的长度,永远无法匹配,所以匹配就没有意义。因此,如果给定文本的长度,则样板的长度一定要小于等于它,即

// 如果
0 < strlen(text) <= n;
0 < strlen(pattern) <= m;
// 那么一定有
m <= n;

要解决匹配问题,最容易想到的就是朴素匹配法,也就是所谓的暴力匹配。

它的做法是从样板的第一个字符一个一个匹配,如果匹配上,则检查样板第二个字符是否匹配,直到完全匹配或者不匹配。如果出现不匹配,那么样板将从文本的第二个字符开始检查,以此类推。例如:

| 123456789  |  123456789  |  123456789  |  123456789  |
+------------+-------------+-------------+-------------+
| bcabasgfd  |  bcabasgfd  |  bcabasgfd  |  bcabasgfd  |
| |          |   |         |    ||||     |     |       |
| *          |   *         |    ^^^*     |     *       |
| abac       |   abac      |    abac     |     abac    |

它唯一的优点就是容易想到,并且容易实现。缺点就是太慢了,以及“时间复杂度”和“空间复杂度”这样的概念,这似乎是优化算法时常用的概念,但是这不是我现在学习的重点,所以不管。


KMP算法的优点在于充分利用了样板pattern的信息,仍然拿上面的字符串举例子:第一次比较一个字符也没匹配上,下次比较,pattern就应该左移一位;第二次同样;第三次匹配到三个字符,第四个字符不匹配,接下来该怎么移动?和暴力匹配一样,把pattern移到第4位置吗?

KMP算法说,或许不需要,或许在这里可以节省时间。

| 123456789  |  123456789  |  123456789  |  123456789  |
+------------+-------------+-------------+-------------+
| bcabasgfd  |  bcabasgfd  |  bcabasgfd  |  bcabasgfd  |
| |          |   |         |    ||||     |      ||     |
| *          |   *         |    ^^^*     |      ^*     |
| abac       |   abac      |    abac     |      abac   |

因为第三次比较时已经匹配上了aba这三个字符,所以不需要移动,我们就知道第4位是b,第5位是a。因此下一次可以直接把pattern移到第5位,而不是第4位。

果然节省了时间;我们首先使用了“匹配数”,是3;然后根据匹配数就能在pattern中找到aba;由于aba的后缀apattern或者说aba的前缀a相等,我们跳过了中间的b移动到了a,也就是跳过4位置,到达5位置。

这种想法怎么实现呢?

答案是利用样板pattern制作一个部分匹配表。比如abac,它的部分匹配表是:

image

匹配数对应一个匹配值,它的特点是只需要一个pattern就能构造,也就是匹配之前生成的。

它的用法是这样的

// 下次的位移 s';匹配数 i;匹配值PI[i];
s' = i - PI[i];

pattern与文本达到对应的匹配数之后,下一次移动的位置就用上面的公式计算。继续看上面的例子:

| 123456789  |  123456789  |  123456789  |  123456789  |
+------------+-------------+-------------+-------------+
| bcabasgfd  |  bcabasgfd  |  bcabasgfd  |  bcabasgfd  |
| |          |   |         |    ||||     |      ||     |
| *          |   *         |    ^^^*     |      ^*     |
| abac       |   abac      |    abac     |      abac   |

在第三步,匹配数是3,对应的匹配值是1,下一次的移动位数是3-1=2,也就是下一次的匹配位置是3+2=5,和前面的看法一致。

根据算法可以写出代码(输出和注释非常详细):

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

// 字符串的KMP算法,用于字符串样板P与待比较字符串的匹配
// 样板P的前缀函数,用于生成样板P的《部分匹配表》 
// 不要让函数改动 P字符数组 ,使用const 
int PAI (const char * a)
{
	// 用于部分匹配表的输出,保存pai值的数组
	int pi[strlen(a)];
	pi[0]=0;
	// 关键在于,计算样板P的,不同匹配数下的,前缀和后缀的最大共同长度 
	// 匹配数从0到样板P的最大长度时,匹配到的字符串,长度当然是从0到样板P的最大长度
	// for循环,用于递增匹配数
	// 从第一位字符开始,因为i从0开始,因此大小是匹配数-1 
	// 这时候字符长度是 i+1 
	for (int i=0;i<strlen(a);i++) 
	{
		printf("========================本轮的匹配数i=%d========================\n",i+1);
		// 用于保存部分匹配数
		int pai=0;
		// 当i到达匹配数对应的字符时,计算这个字符串前缀和后缀的最大共同长度
		// 所谓前缀后缀可以取很长,例如abcdefg的前缀最长是 abcdef,后缀最长是bcdefg 
		// while循环结束之后就能输出字符串前缀和后缀的最大共同长度,也就是部分匹配数  
		// k代表前缀和后缀的长度,长度每轮循环+1
		int k=0;
		while (k<i)
		{
			// 计数器 
			// 每次遍历大一位前缀时计数器清空
			int m=0;
			// 上来就给k+1,符合实际,也方便循环条件比较 
			k++;
			printf("********本次的前缀长度为%d********\n",k);
			// 使用for循环,循环k次,即遍历前缀或者后缀 
			for (int n=0;n<k;n++)
			{
				// 前缀的第一个字符与后缀的第一个字符比较。。。
				printf("前缀第%d个字符a[%d]是:%c\n",n+1,n,a[n]);
				printf("后缀第%d个字符a[%d]是:  %c\n",n+1,i-k+n,a[i+1-k+n]);
				// 也就是p[0]与p[i-1-k], p[1]与p[i-k+1]...p[i-1-1]与p[i-1],从这里也能看出k=i-1
				// 因此使用while (k<i)没问题
				// 如果相等,计数器+1 
				if (a[n]==a[i+1-k+n])
				{
					printf("----->第%d个字符相等,是%c\n",n+1,a[n]);
					m++;
					// 如果计数器大小等于k,即前缀和后缀每一项都相等,赋值给pai 
					if (m==k)
					{
						pai=m;
					}
				}
			}
		}
		printf("部分匹配数pi=%d\n",pai);
		pi[i]=pai;
	}
	// 输出一个部分匹配表 
	printf("=====部分匹配数表=====\n");
	for (int i=0;i<strlen(a);i++)
	{
		printf("PI[%2d]=%2d\n",i+1,pi[i]);
	}
} 

int main ()
{
	//char p[]="ABCDABD";
	//char p[]="ababababca";
	char p[]="abac";
	
	PAI(p);
	
	return 0;
}

下面这个是使用KMP算法进行匹配的代码,根据输出可以看出,一共比较了33次,如果是暴力算法将要比较38次 ,相比之下确实减少了比较次数。

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

void PAI (const char * a,int *pi)
{
	pi[0]=0;

	for (int i=0;i<strlen(a);i++) 
	{
		int pai=0;

		int k=0;
		while (k<i)
		{
			int m=0;
			k++;
			for (int n=0;n<k;n++)
			{
				if (a[n]==a[i+1-k+n])
				{
					m++;
					if (m==k)
					{
						pai=m;
					}
				}
			}
		}
		pi[i]=pai;
	}
} 

// 计算字符串q 
int main ()
{
	char p[]="aba";
	char q[]="casvabcvbuyabaodgaababcasaigdyavgcgdhaba"; 
	int pi[strlen(p)];
	
	PAI(p,pi);
	
	printf("文本长度%d,样板长度%d\n",strlen(q),strlen(p));
	// 样板的末尾不要超出文本 
	{
		// 记录文本的出现次数
		int m=0; 
		// 记录样板在字符串的位置 
		int s=0;
		
		while(s<=strlen(q)-strlen(p))
		{
			
			// 记录匹配数 
			int n=0; 
			// 当p[0]==q[s]开始匹配 
			if (p[0]==q[s])
			{
				printf("第一个字符匹配,现在位置是:%d\n",s);
				n++;
				// 开始从样板的第二个字符匹配 
				for (int i=1;i<strlen(p);i++)
				{
					// 匹配上则匹配数+1 
					if (p[i]==q[s+i])
					{
						printf("第%d个字符匹配\n",i+1);
						n++;
						// 计数 
						if (n==strlen(p)) m++;
					} 
					
				}
				printf("一共匹配%d个字符\n",n);
				// 下一次的移动步数利用公式给出,数组从0开始,所以是pi[n-1] 
				s=s+n-pi[n-1]; 
			} 
			// 第一个就不匹配,移动到下一个位置 
			else
			{
				s++;
			}
			
		}
		// 输出样板出现次数
		printf("样板出现%d次",m); 
	}
	
	return 0;
}



本次学习主要参考站内两位大佬的博客

这位大佬的博客专业性很强,很细,有点难看懂,但是硬看下来的话就觉得都很清楚:
https://www.cnblogs.com/gaochundong/p/string_matching.html
这位大佬把KMP说得很简洁,也有很多图片,很形象,很容易看懂,并且也是C语言:
https://www.cnblogs.com/maybe2030/p/4633153.html

标签:abac,匹配,pattern,bcabasgfd,样板,算法,123456789,KMP,字符串
From: https://www.cnblogs.com/Hubugui/p/17317010.html

相关文章

  • 算法基础模板整理(基础图论篇)
    拓扑排序bool topo(){    queue<int> q;    for(int u = 1; u <= n; u ++ )        if(!ind[u]) q.push(u);        int cnt = 0;    while(!q.empty()){        int u = q.front();        q.pop(); ......
  • 算法基础模板整理(高阶数据结构篇)
    树状数组动态区间和询问+点修改int lowbit(int x){    return x & -x;}void add(int x, int v){    for(int i = x; i <= n; i += lowbit(i)) tree[i] += v;}int query(int x){    int res = 0;    for(int i = x; i......
  • 栈应用——逆波兰算法
    个人主页:【......
  • 人工智能技术的最新进展:机器学习算法的应用与优化
    ​ 人工智能技术的不断发展,机器学习算法已经成为了人工智能领域的重要组成部分。机器学习算法是一种通过数据训练模型,从而使计算机能够自动学习和改进的技术。在过去的几年中,机器学习算法已经在各个领域得到了广泛的应用,包括自然语言处理、图像识别、智能推荐等。在机器学习算法......
  • Mysql_批量替换 MySQL 指定字段中的字符串
    批量替换的具体语法是:UPDATE表名SET 指定字段=replace(指定字段,'要替换的字符串','想要的字符串') WHERE条件;  如果你想把article表中ID小于5000的记录,content字段中“解决”替换成“解放”,那么语法就是:UPDATEarticleSET content=replace(content,'解决',......
  • MATLAB代码:基于模型预测算法的含储能微网双层能量管理模型 模型预测控制 MPC
    MATLAB代码:基于模型预测算法的含储能微网双层能量管理模型   模型预测控制 MPC关键词:储能优化模型预测控制MPC微网优化调度能量管理 参考文档:《ATwo-layerEnergyManagementSystemforMicrogridswithHybridEnergyStorageconsideringDegradationCosts》完......
  • MATLAB代码:基于多目标粒子群算法冷热电联供综合能源系统运行优化
    MATLAB代码:基于多目标粒子群算法冷热电联供综合能源系统运行优化关键词:综合能源冷热电三联供 粒子群算法多目标优化参考文档:《基于多目标算法的冷热电联供型综合能源系统运行优化》仿真平台:MATLAB平台采用粒子群实现求解优势:代码注释详实,适合参考学习,非目前烂大街的版本,......
  • 完善SQL二进制到IP地址字符串转换(Perfecting SQL binary to IP Address string conve
    我们使用二进制(16)字段来存储IP地址。我们这样做,因为它可以同时拥有IPv4和IPv6地址,并且很容易与.NetIPAddress类一起使用。但是,为了报告目的,我创建了以下SQL函数将二进制地址转换为IP地址字符串。CREATEFUNCTIONfn_ConvertBinaryIPAddressToString(@binaryIPbinary(......
  • UVa 10182 Bee Maja (规律&O(1)算法)
    10182-BeeMajaTimelimit:3.000seconds http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=24&page=show_problem&problem=1123Majaisabee.Shelivesinabeehivewiththousandsofotherbees.Thisbeehivecon......
  • 优化 PMU 放置 (OPP) 问题的六种算法,包括两种模拟退火方法、两种图论过程和递归安全 N
    PMU优化配置 系统完全可观软件:MATLAB优化PMU放置(OPP)问题的六种算法,包括两种模拟退火方法、两种图论过程和递归安全N算法。 从MatPower获得的IEEE14,30,39,57和118bus系统数据,可得出系统完全可观所需配置pmu数量以及对应位置。配有对应文献ID:16150671665749743......