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

KMP字符串匹配算法 整理

时间:2023-12-02 22:24:27浏览次数:36  
标签:主串 str2 str1 next 算法 str KMP 字符串

KMP 整理

题面
视频详解

KMP 的作用

KMP 算法的主要作用是求出一个字符串(模式串)是否为另一个字符串(主串)的子串,并同时求出它出现的位置,也即字符串匹配问题。

算法解析

暴力

先说暴力算法:

  • 从头开始枚举模式串位置的起点,然后遍历从起点往后 \(m\) 个字符,检查它是否与模式串完全相同。

  • 复杂度为 \(O(nm)\) ,\(O(n)\) 遍历起点,对于每个起点用 \(O(m)\) 检查是否与模式串相同

我们发现时间复杂度奇高,于是便有三位 dalao (Knuth,Morris,Pratt) 共同发明了 KMP 算法,他的核心思想就是利用已知的数据,利用好,不往回头走,从而做到 \(O(n+m)\) 的线性复杂度。

KMP

KMP 算法思路:

我们设当前比对主串(str1)的下标是 \(i\),模式串(str2)的下标是 \(j\)。当 \(j>m\) 时则字符串匹配;若 \(str1[i]==str2[j]\) 则当前字符匹配,\(i\) 和 \(j\) 同时 ++,开始比较下一个字符 ;若 \(str1[i]!=str2[j]\) ,则说明当前字符不匹配,这时我们跳过一部分字符,\(i\) 不变,继续比较。

举个栗子:

第一次匹配
模式串: ABABC
主串:  ABABABCAB
这时我们发现模式串的C与主串的A不匹配,但是由于我们已经知道了主串的前5个字符,发现模式串的[1,2]串和[3,4]串相同,所以我们直接将模式串跳过前面的[1,2]串AB再进行比较。

第二次匹配
模式串:   ABABC
主串:  ABABABCAB
这时我们发现模式串和主串匹配了,于是说明模式串为主串的字串。

那我们如何知道该跳过夺少字符呢?这时候就要用到 \(next\) 数组了。

next 数组

\(next\) 数组的作用是当我们发现模式串中的第 \(j\) 个字符与主串的第 \(i\) 个字符不匹配时,我们直接跳过前 \(next[j - 1]\) 个字符,\(i\) 不变,这样就可以非常有效地降低时间复杂度。

那我们如何知道 \(next\) 数组的值呢?\(next[i]\) 的本质其实是前 \(i\) 个字符中前缀和后缀完全相同的情况下前后缀的长度最长是多少。举个栗子:

模式串:   A B A B C
next数组: 0 0 1 2 0

\(next\) 数组的前两位为 \(0\) 原因是都没有相同的前后缀而且 \(next[i]\) 不能为 \(i\) ,原因是不能跳过整个子串,不然就没有跳过的意义了。

那 \(next\) 数组的值我们改如何计算呢?我们用递推的方法解决这个问题。首先我们定义一个 \(i\) 和 \(j\) ,分别代表当前要修改哪个 \(next\) 的值和 \(next[i - 1]\) 的值。首先 \(next[1]\) 肯定等于 \(0\) ,原因上面说了是不能跳过整个子串。所以我们从 i = 2, j = 0 开始枚举。由于我们要用递推的算法来计算,于是我们比较 str[i]str[j + 1] ,若两者相等,则说明当前最长前后缀其实是上一个最长前后缀个添加一个相同的字符,此时前后缀依然相等,于是我们执行 j++; next[i] = j; ;而如果两者不同,我们就反复执行 j = next[j]; ,直到 str[i]==str[j + 1] 或者是 j <= 0 ,因为此时我们就已经找到了最长相同前后缀或者当前字串根本不存在最长相同前后缀。

实现流程:

(1)设 \(j\) 为 \(next[i - 1]\) ,比较 str[i]str[j + 1],若相等,那么执行 j++; next[i] = j;

(2)若不相等,\(j\) 需要变得更小,以满足找到最长相同前后缀的最大值,即重复执行j = next[j]; 直到 str[i]==str[j + 1]j <= 0 为止。

Code:

#include<bits/stdc++.h>
using namespace std;
string str1, str2;
const int N = 1e6 + 10;
int nxt[N];
int main() {
	cin >> str1 >> str2;
	for(int i = str1.size(); i >= 1; i--) {
		str1[i] = str1[i - 1];
	}
	for(int i = str2.size(); i >= 1; i--) {
		str2[i] = str2[i - 1];
	}
	for(int i = 2, j = 0; i <= str2.size(); i++) {
		while(j && str2[j + 1] != str2[i]) {
			j = nxt[j];
		}
		if(str2[j + 1] == str2[i]) j++;
		nxt[i] = j;
	}
	for(int i = 1, j = 0; i <= str1.size(); i++) {
		while(j && str2[j + 1] != str1[i]) {
			j = nxt[j];
		}
		if(str2[j + 1] == str1[i]) j++;
		if(j >= str2.size()) {
			cout << i - j + 1 << "\n";
			j = nxt[j]; 
		}
	}
	for(int i = 1; i <= str2.size(); i++) cout << nxt[i] << " ";
	return 0;
}

标签:主串,str2,str1,next,算法,str,KMP,字符串
From: https://www.cnblogs.com/2020luke/p/17872341.html

相关文章

  • 代码随想录算法训练营第四天 | 24. 两两交换链表中的节点 19.删除链表的倒数第N个节
    LeetCode24.两两交换链表中的节点题目链接:LeetCode24思路:交换结点前将cur后第一个结点和第三个结点进行保存,然后修改cur指向头节点后再修改头节点后的结点classSolution{public:ListNode*swapPairs(ListNode*head){ListNode*dummyHead=newListNo......
  • 求最短路径迪杰斯特拉算法
    代码运行截图:完整代码:#include<stdio.h>#include<stdlib.h>#defineMaxSize20#defineMAX999typedefstructArcNode{//边表intadjvex;//边表中是顶点号!!structArcNode*next;intweight;}ArcNode;typedefstructVN......
  • 扩展欧几里得算法
    同余方程\(ax\equivb(\modm)\)二元一次方程\(ax+by=c\),其中\(a,b,c\)为已知的正整数这两者可以相互转化,显然对于这个二元一次方程,有:\(ax\modb=c\modb\),可以转化为\(ax\equivc(modb)\)裴蜀定理当我们考虑一个二元一次方程解的情况,我们发现:1.可能无解2.有解即有......
  • C# 面试常见递归算法
    前言今天我们主要总结一下C#面试中常见递归算法。C#递归算法计算阶乘的方法一个正整数的阶乘(factorial)是所有小于及等于该数的正整数的积,并且0的阶乘为1。自然数n的阶乘写作n!。1808年,基斯顿·卡曼引进这个表示法。原理:亦即n!=1×2×3×...×(n-1)×n。阶乘亦可以递归方式定......
  • Strings字符串
    字符串参考视频链接:【字符串】聪明办法学Python第二版_哔哩哔哩_bilibili用两种不同的引号是为了表达一些在引号里面要用到引号的情况!字符串中的转义字符前面有反斜杠\的字符,叫做转义字符(只能作为一个字符)print("双引号:\"")双引号:"print("反斜线:\\")反斜线:\print(......
  • 循环,字符串,基础文件操作的用法
    Task06:循环Loopand字符串string循环Loopfor循环defsumFromMToN(m,n):total=0#注意:range(x,y)是左闭右开区间,包含x,不包含yforxinrange(m,n+1):total+=xreturntotarange(m,n)左开右闭从m遍历到n-1sumFromMToN(5,10)45r......
  • 算法之快速排序1初始(java)
    一:概述快速排序、归并排序、堆排序等都是比冒泡排序更快的算法。其中快速排序是从冒泡排序演变而来。快速排序之所以比冒泡排序要快是因为它用了分治法。    二:具体说明同冒泡排序一样,快速排序也属于交换排序,通过元素之间的比较进行比较和交换位置来达到排序的目的。不同的是......
  • 循环与字符串
    for循环和循环范围特点:基于特定的范围,重复执行特定次数的操作for循环中循环次数range的表示其中range(x,y)表示包含x,但不包含y,即左闭右开defsumFrom(m,n):returnsum(range(m,n+1))m=5n=10sumFrom(m,n)此时输出就为45,即5+6+7+8+9+10=45range默认起始为0,即range(5)表示0-......
  • 【算法 Java】递归,阶乘的递归实现,斐波那契数列的递归实现
    递归定义:方法直接或间接地调用方法本身思路:将大问题转化为一个与原问题相似的规模更小的问题注意:递归死循环会导致栈内存溢出一些使用递归求解的问题阶乘Factorial.javaimportjava.util.Scanner;publicclassFactorial{publicstaticvoidmain(String[]args)......
  • 聪明办法学python(字符串)
    字符串编写方式单引号,双引号(如果已存在一种,可用另一种引号包裹字符串,或用转义字符),三引号均可原始字符串在字符串前加"r",使字符串内的转义字符不再有效跨行字符串在每一行的末尾加上一个"\"用’‘’‘’‘或”“”“”“包裹字符串字符串字符串的运算1.字符串的......