目录
首先我们需要了解什么是KMP算法?
1、首先这是一个字符串匹配算法,是在暴力(两个for镶套)做法上进行优化从而得到的,与暴力做法相比KMP算法大大减少了时间复杂度;
2、KMP算法,它是在1977年由Knuth、Morris、Pratt联合发表,因此取这三位创始人的首字母最终组成KMP这一名字;
3、KMP主要思想:拿空间换时间;
4、KMP时间复杂度:O(m+n);
5、 KMP主要分两步:求next数组、匹配字符串。
KMP字符串题目概述:
给定一个模式串 S(长串),以及一个模板串 P(短串),所有字符串中只包含大小写英文字母以及阿拉伯数字。模板串 P 在模式串 S 中多次作为子串出现。求出模板串 P 在模式串 S 中所有出现的位置的起始下标。
首先我们会联想到用两个for循环镶套进行依次遍历,即是设置两个指针分别指向模式串和模板串,两个指针从左到右一个个地匹配,如果这个依次向右匹配过程中有某个字符不匹配,模板串(p)指针就跳回到第一位,模式串(s)指针向右移动一位。
暴力求法运行操作图如下所示:
1.首先两者均从下标为0处进行操作;

2.接着两个指针依次往后进行对比,对比相同下标字符是否相同;

3.当相同下标字符不同的情况,i指针向右移动一位,j指针从第一位开始;
(暴力求解过程中两个指针都有回溯,从而浪费了大量的时间导致tql)
暴力求法代码如下所示:
#include <iostream>
using namespace std;
int n, m, t;
string s, p;//s为较长字符串(模式串)
int main()
{
cin >> n >> p >> m >> s;
for (int i = 0; i < m; i++)
{
bool flag = true;// 最开始是匹配成功的状态
for (int j = 0; j < n; j++)
{
if (s[i + j] != p[j])
{
flag = false;//只要有一个不通过,则false
break;
}
}
if (flag == true)
{
cout << i << " ";
}
}
return 0;
}
KMP思想:当出现字符串不匹配的情况时,可以知道一部分之前已经匹配的的文本内容,利用这些信息避免从头再去做匹配,同时比较指针不回溯, 而是直接跳过一些字符串(利用next数组),使得原来模式串中的前缀直接移动到后缀位置上。(其中前缀表担负重任)
KMP运行操作图如下所示:
1.我们可以发现因为主串在到第四位的时候匹配失败,到第四位之前和主串都匹配,如果继续将主串指针右移一位的话就会不匹配。但之前因为我们已经知道前面三个字符都是匹配的,那我们就可以利用这个信息找出接下来指针该移动的位置。思路就是可以让i先不动,只需要移动j即可;

2.在每次失配时,不是把p串往后移一位,而是把p串往后移动至下一次可以和前面部分匹配的位置,这样就可以跳过大多数的失配步骤,从而减少运算次数降低了时间复杂度。而每次p串移动的步数就是通过查找next[ ]数组(接下来会继续介绍)确定的。

前缀:除了最后一个字符以外,该字符串的全部头部组合;
例如:ababa
其前缀为:a、ab、aba、abab;
其后缀为:a、ba、aba、baba;
后缀:除了第一个字符以外,该字符串的全部尾部组合;
例如:ABCDABD
其前缀为:A、AB、ABC、 ABCD、ABCDA、ABCDAB;
其后缀为:D、BD、ABD、DABD、CDABD、BCDABD;
部分匹配表(前缀表):一个字符串的前缀和后缀的最长公有元素的长度;(不包括自身)
用下面的具体示例进行描述:
1、"A"的前缀和后缀都为空集,共有元素的长度为0;
2、"AB"的前缀为{ A },后缀为{ B },共有元素的长度为0;
3、"ABC"的前缀为{ A, AB },后缀为{ C, BC },共有元素的长度0;
4、"ABCD"的前缀为{ A, AB, ABC },后缀为{ D, CD, BCD },共有元素的长度为0;
5、"ABCDA"的前缀为{ A, AB, ABC, ABCD },后缀为{ A, DA, CDA, BCDA },共有元素为"A",长度为1;
6、"ABCDAB"的前缀为{ A, AB, ABC, ABCD, ABCDA },后缀为{B, AB, DAB, CDAB, BCDAB],共有元素为"AB",长度为2;
7、"ABCDABD"的前缀为{A, AB, ABC, ABCD, ABCDA, ABCDAB},后缀为{D, BD, ABD, DABD, CDABD, BCDABD},共有元素的长度为0。
idx : 数组的下标;
ne[i] : 以i为结尾的部分匹配的值,ne数组即为前缀表;
例如:aabaaf
p[N] a a b a a f
idx 1 2 3 4 5 6
ne[N] 0 1 0 1 2 0
1、ne[N]数组的含义:它是一个前缀表,或者说是一个前缀表达的某种变形 ,目的是储存p[ 1, j ]串中前缀和后缀相同的最大长度(部分匹配值);
2、通过求部分匹配值求出ne[N]的值;
3、ne[N]是一个前缀表,它的作用是用来回退的,它记录了模式串和模板串不匹配时,模板串应该从哪里重新匹配的信息;
模板串每次向后移动位数:移动位数 = 已匹配的字符数 - 对应的部分匹配值***很重要***
移动位数 = 已匹配的字符数 - 对应的部分匹配值(最长共有元素长度)
例如:
s串:aabaabaaf
p串:aabaaf
1、首先p串与s串的前五位相互匹配,但当p串的第六位(f)与s串(b)不匹配时,此时就要利用KMP的方法将p串进行向后移动;
2、求p串中出最长公有元素的长度(储存在ne[N]中);(本例题中最长共有元素长度为2)
3、移动位数 = 已匹配的字符数 - 对应的部分匹配值;(本例题移动位数为“5-2=3”)
4、移动之后为:
s串:aabaabaaf
p串: aabaaf
主要有如下三步:
一、初始化ne数组;
二、处理后缀不同的情况;
三、处理后缀相同的情况;
一、初始化ne数组;
首先定义两个指针i和j,j是指向前缀的起始位置,i是指向后缀的起始位置,然后对ne数组进行初始化赋值。
int j = 0;
ne[0] = j;
二、处理后缀不同的情况;
因为j是从0开始的,所以i需要从2开始,比较p[i]与p[j+1]是否相同,遍历模式串循环下标从2开始;
for (int i = 2 ; i <= n; i++)
如果s[i]与s[j+1]不相同,也就是前后缀末尾不同的情况,那该怎么会退呢??
答案就是如果ne[j]记录了j之前的子字符串的相同前后缀的长度,如果s[i]!=s[j+1],则要查找下标j+1的前一元素在ne数组中的值。代码如下:
while (j && p[i] != p[j + 1])//前后缀不相同
{
j = ne[j];//向后退
}
三、处理前后缀相同的情况;
如果s[i]==s[j+1],那么就同时向后移动i和j,说明找到了相同的前后缀,同时还要将j(前缀的长度)赋值给ne[i],因为ne[i]要记录相同的前后缀的长度。代码如下
if (p[i] == p[j + 1]) j++;//找到相同的前后缀
ne[i] = j
整体构建ne[N]数组代码如下:
// 构建ne数组,表示匹配不成功时可以直接向后跳多少位
for (int i = 2, j = 0; i <= n; i ++ )
{
while (j && p[i] != p[j + 1]) j = ne[j];
if (p[i] == p[j + 1]) j ++ ;
// 不匹配了,j此时是最大的前缀后缀相等的长度,将其赋值给ne[i],最大长度就是跳转的位置
ne[i] = j;
}
题目描述:
给定一个字符串 S,以及一个模式串 P,所有字符串中只包含大小写英文字母以及阿拉伯数字。模式串 P 在字符串 S 中多次作为子串出现;
求出模式串 P 在字符串 S 中所有出现的位置的起始下标。
输入格式
第一行输入整数 N,表示字符串 P 的长度。
第二行输入字符串P。
第三行输入整数 M,表示字符串 S 的长度。
第四行输入字符串 S。
输出格式
共一行,输出所有出现位置的起始下标(下标从 0 开始计数),整数之间用空格隔开。
数据范围
1<=N<=1e5
1<=M<=1e6
输入样例:
3 aba 5 ababa输出样例:
0 2
详解代码模板:
#include<iostream>
using namespace std;
const int N = 100010, M = 1000010;
char q[N], s[M];//q为模式串(长串),s为模板串(短串)
int ne[N], n, m;
int main()
{
cin >> n >> q + 1 >> m >> s + 1;//q,s的下标均从1开始
for (int i = 2, j = 0; i <= n; i++)//j表示匹配成功的长度,i表示q数组中的下标
//因为q数组的下标是从1开始的,只有1个时,ne[1]一定为0,所以i从2开始
{
while (j && q[i] != q[j + 1]) j = ne[j]; //如果不行可以换到next数组
if (q[i] == q[j + 1]) j++;//成功了就加1
ne[i] = j;//对应其下标
}
//j表示匹配成功的长度,因为刚开始还未开始匹配,所以长度为0
for (int i = 1, j = 0; i <= m; i++)
{
while (j && s[i] != q[j + 1]) j = ne[j];
//如果匹配不成功,则换到j对应的next数组中的值
if (s[i] == q[j + 1]) j++;
if (j == n)//说明已经完全匹配上去了
{
printf("%d ", i - j);
//因为题目中的下标从0开始,所以i-j不用+1;
j = ne[j];
//为了观察其后续是否还能跟S数组后面的数配对成功
}
}
return 0;
}
简介模板代码:
#include<iostream>
using namespace std;
const int N = 100010, M = 1000010;
char q[N], s[M];
int ne[N], n, m;
int main()
{
cin >> n >> q + 1 >> m >> s + 1;
for (int i = 2, j = 0; i <= n; i++)
{
while (j && q[i] != q[j + 1]) j = ne[j];
if (q[i] == q[j + 1]) j++;
ne[i] = j;
}
for (int i = 1, j = 0; i <= m; i++)
{
while (j && s[i] != q[j + 1]) j = ne[j];
if (s[i] == q[j + 1]) j++;
if (j == n)
{
printf("%d ", i - j);
j = ne[j];
//为了观察其后续是否还能跟S数组后面的数配对成功
}
}
return 0;
}
我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden
目录一.加解密算法数字签名对称加密DES(DataEncryptionStandard)3DES(TripleDES)AES(AdvancedEncryptionStandard)RSA加密法DSA(DigitalSignatureAlgorithm)ECC(EllipticCurvesCryptography)非对称加密签名与加密过程非对称加密的应用对称加密与非对称加密的结合二.数字证书图解一.加解密算法加密简单而言就是通过一种算法将明文信息转换成密文信息,信息的的接收方能够通过密钥对密文信息进行解密获得明文信息的过程。根据加解密的密钥是否相同,算法可以分为对称加密、非对称加密、对称加密和非
华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o
C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.
MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO
遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg
通常,数组被实现为内存块,集合被实现为HashMap,有序集合被实现为跳跃列表。在Ruby中也是如此吗?我正在尝试从性能和内存占用方面评估Ruby中不同容器的使用情况 最佳答案 数组是Ruby核心库的一部分。每个Ruby实现都有自己的数组实现。Ruby语言规范只规定了Ruby数组的行为,并没有规定任何特定的实现策略。它甚至没有指定任何会强制或至少建议特定实现策略的性能约束。然而,大多数Rubyist对数组的性能特征有一些期望,这会迫使不符合它们的实现变得默默无闻,因为实际上没有人会使用它:插入、前置或追加以及删除元素的最坏情况步骤复
在ruby中,你可以这样做:classThingpublicdeff1puts"f1"endprivatedeff2puts"f2"endpublicdeff3puts"f3"endprivatedeff4puts"f4"endend现在f1和f3是公共(public)的,f2和f4是私有(private)的。内部发生了什么,允许您调用一个类方法,然后更改方法定义?我怎样才能实现相同的功能(表面上是创建我自己的java之类的注释)例如...classThingfundeff1puts"hey"endnotfundeff2puts"hey"endendfun和notfun将更改以下函数定
我目前有一个reddit克隆类型的网站。我正在尝试根据我的用户之前喜欢的帖子推荐帖子。看起来K最近邻或k均值是执行此操作的最佳方法。我似乎无法理解如何实际实现它。我看过一些数学公式(例如k表示维基百科页面),但它们对我来说并没有真正意义。有人可以推荐一些伪代码,或者可以查看的地方,以便我更好地了解如何执行此操作吗? 最佳答案 K最近邻(又名KNN)是一种分类算法。基本上,您采用包含N个项目的训练组并对它们进行分类。如何对它们进行分类完全取决于您的数据,以及您认为该数据的重要分类特征是什么。在您的示例中,这可能是帖子类别、谁发布了该项
我查看了Stripedocumentationonerrors,但我仍然无法正确处理/重定向这些错误。基本上无论发生什么,我都希望他们返回到edit操作(通过edit_profile_path)并向他们显示一条消息(无论成功与否)。我在edit操作上有一个表单,它可以POST到update操作。使用有效的信用卡可以正常工作(费用在Stripe仪表板中)。我正在使用Stripe.js。classExtrasController5000,#amountincents:currency=>"usd",:card=>token,:description=>current_user.email)