数据结构:KMP算法的原理图解和代码解析

文章目录

本篇总结的是关于串中的KMP算法解析

应用场景

现给定两个串,现在要看较短的一个串是不是较长的串的子串,如果是就输出子串后面的内容,如果不是则输出Not Found

能匹配到:

长串:qwertabcde

短串:abcd

则可以在长串中找到短串的内容,则输出abcde

匹配不到:

长串:qwertabcde

短串:afcd

则无法在长串中匹配到短串的内容,则输出Not Found

算法方案

对于如何匹配串的问题,首先是一种暴力的方案,例如让短串的内容不断地和长串进行匹配,如果在短串和长串中对应到了,就两个同时向后移动,如果短串到头,就说明匹配成功了,如果遇到其他字符,就重新进行匹配,这就是暴力求解的方案,但是时间复杂度高,总体来说是一个O(MN)的时间复杂度

这样的时间复杂度对于算法来说是比较高的,于是有三个大佬Knuth、Morris、Pratt,发明了一个著名的字符串匹配算法,因此这个算法的名字就被命名为KMP算法

算法原理

为了方便叙述,定义str是这里的长串,pattern是要匹配的串

算法原理就是创建一个next数组,里面存储的是pattern中,下标为i的字符前的字符串最长相等前后缀的长度

那什么是最长相等前后缀?用下面的例子来举例:

假设现在pattern是abcab,那么对于pattern来说,它的前后缀分别有:

前缀:{a,ab,abc,abca,abcab}

后缀:{b,ab,cab,bcab,abcab}

因此对于pattern来说,它的next数组可以这么表示

从pattern的最后一个字符来看,它的前面的字符串是abca,而对于这个串来说的相等的前后缀只有a这一个,因此这里填入的就是a的长度也就是1

但是这个数组有什么用?从下面这个例子来看:

假设现在str为abcabeabcabcmn,pattern为abcabcmn

那么写出pattern的next数组:

下面就开始进行匹配了,当匹配到e和c的时候匹配失败了,此时如果是暴力算法的思路来看,需要让pattern和str的第二个字符开始对齐,再重新匹配,但是对于KMP算法来说,next数组的作用就出现了

只需要让不匹配的字符下标对应的next下标的值,回溯到pattern下标即可

以上面的例子为例,现在是s[5]和p[5]的匹配失败了,那么next[5]对应的数据是2,那么就意味着现在要让s[5]和p[2]进行对齐匹配,也就是说,设匹配失败的字符下标为i,那么就要让s[i]和p[next[i]]进行匹配

这样就是一个循环了,进行多次循环即可,这也就是KMP算法的核心所在

next数组的意义:

  1. 下标为i的字符前的字符串最长相等前后缀的长度
  2. 该处字符不匹配时应该回溯到的字符的下标

上面的next数组写法只是手算出来的,在实际算法中需要将next数组用代码实现写出来:

cpp 复制代码
void GetNext(const string& pattern, vector<int>& next)
{
	int i = 0, j = -1;
	next[0] = -1;
	while (i < pattern.size() - 1)
	{
		if (j == -1 || pattern[i] == pattern[j])
		{
			next[++i] = ++j;
		}
		else
		{
			j = next[j];
		}
	}
}

对于上面的代码来进行解析:

  1. 如果两个i和j的对应的字符相等,那么i和j就同步向后移动
  2. 如果不相等,就要进行回退了,回退到它们原来最长公共前后缀的地方,i指向的是后面的最长公共前后缀,j回退到前面的最长公共前后缀,如果这两个前后缀相等,那么这就组成了一个新的最长相等前后缀,就可以进行数据的写入了

关于求出next数组后,利用这个数组求KMP算法的代码:

cpp 复制代码
int KMP(const string& str, const string& pattern, const vector<int>& next)
{
	int i = 0, j = 0;
	while (i < (int)str.size() && j < (int)pattern.size())
	{
		if (j == -1 || str[i] == pattern[j])
		{
			i++, j++;
		}
		else
		{
			j = next[j];
		}
	}

	if (j == pattern.size())
	{
		return i - j;
	}
	else
	{
		return -1;
	}
}

在知道next数组后,解决剩下的问题就很容易了,只需要一一进行比对,如果不满足条件就进行回溯,如果走到头就返回下标,如果不满足条件就返回-1

完整代码

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

// KMP算法,给定两个字符串,用子串去匹配长字符串,如果匹配成功就输出匹配的字符串和后面的内容
// 如果匹配不成功就输出NOT FOUND

void GetNext(const string& pattern, vector<int>& next)
{
	int i = 0, j = -1;
	next[i] = j;
	while (i < pattern.size() - 1)
	{
		if (j == -1 || pattern[i] == pattern[j])
		{
			next[++i] = ++j;
		}
		else
		{
			j = next[j];
		}
	}
}

int KMP(const string& str, const string& pattern, const vector<int>& next)
{
	int i = 0, j = 0;
	while (i < (int)str.size() && j < (int)pattern.size())
	{
		if (j == -1 || str[i] == pattern[j])
		{
			i++, j++;
		}
		else
		{
			j = next[j];
		}
	}

	if (j == pattern.size())
	{
		return i - j;
	}
	else
	{
		return -1;
	}
}

void PrintString(const string& str, int index)
{
	string res;
	for (int i = index; i < str.size(); i++)
	{
		res += str[i];
	}
	cout << res << endl;
}

int main()
{
	// str是长字符串,pattern是要匹配的子串
	string str, pattern;
	cin >> str >> pattern;

	// KMP算法首先计算出pattern的next数组
	vector<int> next(pattern.size());
	GetNext(pattern, next);

	// 根据str,pattern,next数组进行匹配
	int index = KMP(str, pattern, next);

	// 得出结果
	if (index == -1)
	{
		cout << "NOT FOUND" << endl;
	}
	else
	{
		PrintString(str, index);
	}

	return 0;
}
相关推荐
nhdh38 分钟前
B+树揭秘:MySQL索引核心原理全解析
数据结构·b树·mysql
海上小飞龙1 小时前
【KMP算法-下篇】同一道题:Java 库函数 2600 微秒,手写 KMP 16 微秒
java·开发语言·算法
青少儿编程课堂1 小时前
后缀自动机解析:本质不同子串与最长重复片段统计
c++·python·算法·bfs·信息学竞赛
朝朝辞暮i1 小时前
C++ 第 12 课:局部变量、作用域、变量生命周期
开发语言·c++·算法
乐迪信息3 小时前
港口船舶逆行怎么管?AI防爆摄像机搭载智能检测算法
大数据·人工智能·深度学习·算法·计算机视觉
Sirens.3 小时前
Java并发锁详解:六类锁策略与 synchronized 底层原理
java·前端·算法
和裕4 小时前
全纸结构重型纸箱能否满足 1 吨以上设备出口熏蒸豁免要求?通关合规性全解析
大数据·运维·网络·人工智能·算法
tangguofeng4 小时前
不带头结点的链队操作集(不含InitFlag)(C语言版)
算法
苏打豆6 小时前
CRISP源码阅读——基于ROS2力矩反馈控制器与速度加速度零空间
c++·线性代数·算法·矩阵·机器人
蛋蛋的就会顺顺的6 小时前
hot100——矩阵
java·数据结构·算法·leetcode·力扣