首页 > 编程语言 >字符串KMP算法详解

字符串KMP算法详解

时间:2024-02-14 23:13:55浏览次数:32  
标签:匹配 int KMP 算法 详解 ull kmp 字符串

引入

字符串kmp算法用于解决字符串匹配的问题:

给出两个字符串 \(s_1\) 和 \(s_2\),若 \(s_1\) 的区间 \([l, r]\) 子串与 \(s_2\) 完全相同,则称 \(s_2\) 在 \(s_1\) 中出现了,其出现位置为 \(l\)。 现在请你求出 \(s_2\) 在 \(s_1\) 中所有出现的位置。

  • 很显然,我们能够想到暴力求解:
cin>>s1>>s2;
ll lena=s1.size(),lenb=s2.size();
for(int i=0;i<=lena-lenb;++i){
  bool flag=0;
  for(int j=i,k=1;j<i+lenb;++j,++k){
      if(s1[j]!=s2[k]){
           flag=1;
           break;
      }
  }
  if(!flag)cout<<i+1<<'\n';
}

时间复杂度为 \(O(nm)\) ,显然是不被接受的。

#include<iostream>
#include<cstdio>
#include<cstring>
#define ull unsigned long long
using namespace std;
const int N=1e6+10;
ull h[N],hs=0;
char s[N],t[N];
ull qp(ull x,ull y)
{
  ull now=x,ans=1;
  while(y)
  {
      if(y&1)
          ans*=now;
      now*=now;
      y>>=1;
  }
  return ans;
}
int main()
{
  cin>>s+1>>t+1;
  int l1=strlen(s+1),l2=strlen(t+1);
  for(int i=1;i<=l1;++i)
      h[i] = h[i-1]*131ull+s[i];
  for(int i=1;i<=l2;++i)
      hs = hs*131ull+t[i];
  int ans=0;
  for(int i=l2;i<=l1;++i)
  {
      ull now=h[i]-h[i-l2]*qp(131,l2);
      if(now==hs)
          ans++;
  } 
  cout<<ans<<endl;
  return 0;
}

我们假设所给定的字符串允许 \(k\) 次失配 , 时间复杂度为:\(O(m+kn· log_2^{m})\)

即使算法加入二分优化也卡不过硬性的匹配数据。

—— 那么,我们就可以考虑 字符串kmp算法

字符串kmp

设 \(s1 = a b a c a b a b , s2 = ababc\)

一开始,我们从 \(i=0\) 开始匹配:

kmp算法的具体思路是当我们发现某一个字符串不匹配的时候,由于已经知道之前遍历过的字符,利用这些信息来做一个 backup 的操作。

用上面的例子,我们在主串中搜索 \(ababc\) 发现最后一个字符不匹配。由于我们知道上面读过那些字符,我们将字串移动到这个位置:

接着进行匹配,由于这里的 \(AB\) 和主串的 \(AB\) 相同,我们完全可以跳过他们,直接进行下一步的匹配:

那我们怎么知道要跳过多少个字符呢?

——那就要用到 kmp 算法中的 next 数组了。

kmp算法在匹配失败的时候:

我们回去看最后一个匹配字符的next值:

例如此处是 \(2\) 然后直接跳过 \(2\) 个字符:

这里 \(2\) 代表子串中我们可以跳过的字符,也就是说前面的这个 \(AB\) 不需要看了,直接进行下一步匹配:

很显然,这样操作是没有问题的,因为主串中跳过的这两个 \(AB\) 确实可以和子串中的 \(AB\) 匹配上。所以我们只需要继续匹配后面的字符即可。

由于不用把时间浪费在无意义的失配上,效率自然也提高了不少。

标签:匹配,int,KMP,算法,详解,ull,kmp,字符串
From: https://www.cnblogs.com/SCAtlas-lxy23/p/18015804

相关文章

  • Tarjan 算法
    part1:离线求\(lca\)将走过的点且未回溯的标记为\(1\),已回溯的标记为\(2\),未访问标记为\(0\)把对于一点\(x\)的询问\(y\)存进相应的\(vector\),当回溯时判断如果询问的点标记为\(2\),则\(lca\)为询问的点\(y\)到根的路径上最早标记为\(1\)的点但直接找复杂度不对......
  • 使用lanczos算法进行的预处理共轭梯度算法(Preconditioned Conjugate Gradients Metho
    构造预处理矩阵M(对称正定)下图来自:预处理共轭梯度法(1)......
  • exec 函数详解及应用
    exec函数详解及应用一、介绍​ 当谈论exec函数时,我们通常指的是exec函数族,这是在Unix/Linux操作系统上用于执行新进程的一组系统调用。exec函数族的成员包括execl、execv、execle、execve、execvp等。这些函数的主要目的是在当前进程的上下文中执行一个新程序,从而替换......
  • 代码随想录算法训练营第十七天| 110.平衡二叉树 257. 二叉树的所有路径 404.左叶
    110.平衡二叉树 题目链接:110.平衡二叉树-力扣(LeetCode)思路:判断平衡二叉树,就是判断两个子树的高度差,继而问题转化为了如何求子树的高度——后序遍历(主要卡在了这里)。递归函数返回的是树的高度,同时用-1来表示退出递归(一开始想着用bool型作为返回值,发现函数不好设计)。同时要关......
  • URL编码算法:解决特殊字符在URL中的烦恼
    引言:URL编码算法是一种将URL中的特殊字符转换为特定格式的编码方式。它在网络传输中起到了保护数据安全与完整性的重要作用。本文将深入探讨URL编码算法的优点与缺点,并介绍它在Web开发、网络安全等方面的应用。URL编码解码|一个覆盖广泛主题工具的高效在线平台(amd794.com)h......
  • 【算法】【动态规划】钢条切割
    1 题目来自算法导论的一道经典题目:2 解答动态规划原理虽然已经用动态规划方法解决了上面问题,但是大家可能还跟我一样并不知道什么时候要用到动态规划。总结一下上面的斐波拉契数列和钢条切割问题,发现两个问题都涉及到了重叠子问题,和最优子结构。①最优子结构用动态规......
  • 预处理共轭梯度算法(Preconditioned Conjugate Gradients Method)
    预处理共轭梯度算法(PreconditionedConjugateGradientsMethod)给出百度百科上的解释:预处理共轭梯度法预处理共轭梯度法是。不必预先估计参数等特点。共轭梯度法近年来在求解大型稀疏方程组中取得了较好的成效。理论上普通的共扼梯度法对于对称超正定方程,只要迭代步数达到......
  • leetcode——数组算法——前缀和构建和应用
    leetcode——数组算法——前缀和构建和应用前缀和技巧适用于快速、频繁地计算一个索引区间内的元素之和303.区域和检索-数组不可变比如leetcode303.区域和(检索-数组不可变)题目介绍:给定一个整数数组nums,处理以下类型的多个查询:计算索引left和right(包含left......
  • 分布式事务详解
    概述随着互联网的发展,软件系统由原来的单体应用转变为分布式应用。分布式系统把一个单体应用拆分为可独立部署的多个服务,因此需要服务与服务之间远程协作才能完成事务操作。这种分布式系统下不同服务之间通过远程协作完成的事务称之为分布式事务,例如用户注册送积分事务、创建订单......
  • 【算法】DFS
    DFSDFS是一种遍历或搜索图、树或图像等数据结构的算法,当然这个图、树未必存储下来。常见的是通过某种关系构造出的搜索树,搜索树一般是排列型搜索树和子集型搜索树。DFS一般使用栈或递归。一道模板题#include<bits/stdc++.h>usingnamespacestd;constintN=50;intn,m......