写在前面

今天是农历大年初三,祝大家新年快乐!

尽管新旧交替只是一个瞬间,在大家互祝新年快乐的瞬间,在时钟倒计时数到零的瞬间,在烟花在黑色幕布绽放的瞬间,在心底默默许下愿望的瞬间……跨入新的一年,并不意味了一切都会朝着更美好,也没有什么会从天而降,我们赋予了它这份意义,让它自然裹挟着新的爱与希望而来。

当我的视线跃过癸卯兔年,一路的海浪翻涌千叠阳关,才发现此间飘零无关风月,只是山海与风又如期周游了人间一趟。

《人民日报》说,人生这条路很长,未来星辰大海般璀璨,不必踌躇于过去的半亩方塘,这些所谓的遗憾,可能是一种成长,那些曾受过的伤,终会化作光照亮前方的路。

总有一天你会明白,真正治愈你的从来都不是时间,而是你心理的那份释怀与格局,只要你的内心不慌乱,连世界都难影响你。

串的定义和实现

串的定义

串(string)是由零个或多个字符组成的有限序列。一般记为:

\[S = 'a_1 a_2 \dots a_n' \quad (n \geq 0)
\]

其中\(S\)是串名,\(a_i\)可以是字母,数字或其他字符。

串中任意连续多个的字符组成的子列称为该串的子串,包含子串的串称为主串。某个字符在串中的序号称为该字符在串中的位置

\(e.g.\) 串\(A = ' \text{China Beijing} ', B = ' \text{Beijing} ', C = ' \text{China} '\),则他们的长度分别是13,7,5,B和C是A的子串,B在A的位置是7。

串的存储结构

定长顺序存储表示

在串的定长顺序存储结构中,每个串变量分配一个固定长度的存储区:

#define MaxLen 255
typedef struct {
char ch[MaxLen]; // 每个分量存储一个字符
int length; // 串的实际长度
}String;

串长只能小于等于MaxLen,超过预定义长度的串被舍去,称为截断

串长的两种表述方式:

  • 用一个额外的变量len来存放串的长度
  • 在串值后面加一个不计入长度的结束标记字符"\(\text{\\ 0}\)",此时串长为隐含值

堆分配存储表示

堆分配存储表示以一组地址连续的存储单元存放串值的字符序列,但存储空间是在程序执行过程中动态分配得到

typedef struct {
char *ch;
int length;
}String;

串的匹配模式

简单的模式匹配算法

子串的定位操作通常称为串的模式匹配,它求的是子串(模式串)在主串中的位置。

思想:从文本串的第一个字符开始匹配,如果当前字符匹配成功,则继续匹配下一个字符,否则回溯到上一个匹配的位置,重新从下一个位置开始匹配。如果模式串已经遍历完,说明匹配成功,返回匹配的起始位置,否则匹配失败,返回-1。

int simple_match(char *s, char *p) {
int i = 0, j = 0;
while(s[i] != '\0' && p[j] != '\0') {
if(s[i] == p[j])
i ++, j ++;
else {
i = i - j + 1;
j = 0;
}
}
if(p[j] == '\0')
return i - j;
else
return -1;
}

这是一种暴力匹算法,时间复杂度为\(O(mn)\),m和n分别表示主串和模式串的长度。

我们设主串\(S = 'ababcabcacbab'\),模式串\(p = 'abcac'\)进行图解,后文图解两串同。

KMP算法

如上图所示,在第三趟匹配中,\(i = 7, j = 5\)的字符比较,结果不等,于是回退到\(i = 4, j = 1\)重新比较。其实我们可以发现,中间第四、五、六趟比较其实都是不必进行的,可以直接到\(i = 7, j = 2\)开始比较。

字符串的前缀、后缀和部分匹配值

前缀:除最后一个字符以外,字符串的所有头部子串;

后缀:除第一个字符外,字符串的所有尾部子串;

部分匹配值:字符串的前缀和后缀的最长相等前后缀长度;

以\('ababa'\)为例:

我们可以得到该字符串的部分匹配值为:00123

回到最初的问题,将模式串改写成数组的形式,得到部分匹配值表(PM表)

编号 1 2 3 4 5
S a b c a c
PM 0 0 0 1 0

记住一个公式,非常重要:

\[移动位数= 已匹配的字符数 - 对应的部分匹配值
\]

下面用PM表来进行字符串匹配:

第一趟匹配过程:发现c与a不匹配,前面两个字符ab匹配,查表,最后一个匹配字符b对应的部分匹配值是0,由公式可得,将子串向后移动2位,进行第二趟匹配:

第二趟匹配过程:发现c与b不匹配,前面四个字符abca匹配,最后一个匹配字符a对应的部分匹配值为1,由公式可得,将子串向后移动3位,进行第三趟匹配:

第三趟匹配过程:匹配成功。

整个匹配过程中,主串没有发生过回退,所以KMP算法可以在\(O(m+n)\)的时间数量级上完成串的模式匹配。

KMP算法的原理

如图2所示,当c与b不匹配时,已匹配的\('abca'\)的前缀a和后缀a为最长公共元素,因此无需比较,直接将子串按照公式移动对应位数,如图3所示:

对算法的改进方法:

已知:右移位数 = 已匹配的字符数 - 对应的部分匹配值;

写成:\(Move = (j - 1) - PM[j - 1]\)。

使用部分匹配值时,每当匹配失败,就去找它前一个元素的部分匹配值,将PM表右移一位,方便直接看自己的部分匹配值。

将例子中模式串PM表右移一位,得到next数组:

编号 1 2 3 4 5
S a b c a c
PM 0 0 0 1 0
next -1 0 0 0 1
  • 第一个元素右移以后空缺的用-1来填充,因为若第一个元素匹配失败,则需要将子串右移一位,而不需要计算子串移动的位数;
  • 最后一个元素在右移时溢出,因为原来的子串中最后一个元素的部分匹配值是下一个元素使用的,但已经没有下一个元素了,舍去。

于是上式改写成:\(Move = (j - 1) - next[j]\)

最终得到子串指针变化公式 \(j = next[j]\)。在实际匹配过程中,子串在内存中是不会移动的,而是指针的变化。

\(next[j]\)的含义是:当子串的第i个字符与主串发生失配时,跳到子串的next[j]位置重新与主串当前位置进行比较

本人在网上查阅相关资料时,没有找到任何有关next数组的推导公式证明,此外由于本人近日感冒腰扭了,坐着打字痛的一批,后面我好了看到这里再补充,接下来我们直接看求next值的代码:

void get_next(char *p, int *next) {
int i = 0, j = -1;
next[0] = -1;
while(p[i] != '\0') {
if(j == 1 | p[i] == p[j]) {
i ++, j ++;
next[i] = j;
}
else {
j = next[j];
}
}
}

KMP的代码:

int index_KMP(char *s, char *p, int *next) {
int i = 0, j = 0;
int s_len = strlen(s), p_len = strlen(p);
while(i < s_len && j < p_len) P
if(j == -1 || s[i] == p[j])
i ++, j ++;
else
j = next[j];
if(p[j] == '\0')
return i - j;
else
return -1;
}

题目:来自AcWing 831

给定一个字符串 S,以及一个模式串 P,所有字符串中只包含大小写英文字母以及阿拉伯数字。

模式串 P 在字符串 S 中多次作为子串出现。

求出模式串 P 在字符串 S 中所有出现的位置的起始下标。

输入格式

第一行输入整数 N,表示字符串 P 的长度。

第二行输入字符串 P。

第三行输入整数 M,表示字符串 S 的长度。

第四行输入字符串 S。

输出格式

共一行,输出所有出现位置的起始下标(下标从 0 开始计数),整数之间用空格隔开。

数据范围

\(1 \leq N \leq 10^5\)

\(1 \leq M \leq 10^6\)

输入样例

3

abc

5

ababa

输出样例

0 2

完整代码

#include<iostream>
using namespace std; const int N = 100000 + 10, M = 1000000 + 10;
int n, m;
char p[N], s[M];
int next[N]; int main() {
cin >> n >> p + 1 >> m >> s + 1;
for(int i = 2, j = 0; i <= n; i ++) {
while(j && p[i] != p[j + 1])
j = next[j];
if(p[i] == p[j + 1])
j ++;
ne[i] = j;
}
for(int i = 1, j = 0; i <= m; i ++) {
while(j && s[i] != p[j + 1])
j = next[j];
if(s[i] == p[j + 1])
j ++;
if(j == n) {
printf("%d ", i - n);
j = next[j];
}
}
return 0;
}

KMP的进一步优化

这里也仅给出代码,后面再补吧,菜鸡博主痛得不行了

void get_nextval(char *p, int *nextval) {
int i = 0, j = -1;
nextval[0] = -1;
while(p[i] != '\0') {
if(j == -1 || p[i] == p[j]) {
i ++, j ++;
if(p[i] != p[j])
nextval[j] = j;
else
nextval[i] = nextval[j];
}
else
j = nextval[j];
}
}

[数据结构] 串与KMP算法详解的更多相关文章

  1. 算法进阶面试题01——KMP算法详解、输出含两次原子串的最短串、判断T1是否包含T2子树、Manacher算法详解、使字符串成为最短回文串

    1.KMP算法详解与应用 子序列:可以连续可以不连续. 子数组/串:要连续 暴力方法:逐个位置比对. KMP:让前面的,指导后面. 概念建设: d的最长前缀与最长后缀的匹配长度为3.(前缀不能到最后一 ...

  2. 数据结构4.3_字符串模式匹配——KMP算法详解

    next数组表示字符串前后缀匹配的最大长度.是KMP算法的精髓所在.可以起到决定模式字符串右移多少长度以达到跳跃式匹配的高效模式. 以下是对next数组的解释: 如何求next数组: 相关链接:按顺序 ...

  3. kmp算法详解

    转自:http://blog.csdn.net/ddupd/article/details/19899263 KMP算法详解 KMP算法简介: KMP算法是一种高效的字符串匹配算法,关于字符串匹配最简 ...

  4. [转] KMP算法详解

    转载自:http://www.matrix67.com/blog/archives/115 KMP算法详解 如果机房马上要关门了,或者你急着要和MM约会,请直接跳到第六个自然段.    我们这里说的K ...

  5. KMP算法详解(转自中学生OI写的。。ORZ!)

    KMP算法详解 如果机房马上要关门了,或者你急着要和MM约会,请直接跳到第六个自然段. 我们这里说的KMP不是拿来放电影的(虽然我很喜欢这个软件),而是一种算法.KMP算法是拿来处理字符串匹配的.换句 ...

  6. KMP算法详解&&P3375 【模板】KMP字符串匹配题解

    KMP算法详解: KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt(雾)提出的. 对于字符串匹配问题(such as 问你在abababb中有多少个 ...

  7. KMP算法详解 --- 彻头彻尾理解KMP算法

    前言 之前对kmp算法虽然了解它的原理,即求出P0···Pi的最大相同前后缀长度k. 但是问题在于如何求出这个最大前后缀长度呢? 我觉得网上很多帖子都说的不是很清楚,总感觉没有把那层纸戳破, 后来翻看 ...

  8. 字符串匹配KMP算法详解

    1. 引言 以前看过很多次KMP算法,一直觉得很有用,但都没有搞明白,一方面是网上很少有比较详细的通俗易懂的讲解,另一方面也怪自己没有沉下心来研究.最近在leetcode上又遇见字符串匹配的题目,以此 ...

  9. KMP算法详解-彻底清楚了(转载+部分原创)

    引言 KMP算法指的是字符串模式匹配算法,问题是:在主串T中找到第一次出现完整子串P时的起始位置.该算法是三位大牛:D.E.Knuth.J.H.Morris和V.R.Pratt同时发现的,以其名字首字 ...

  10. 模式匹配的KMP算法详解

    这种由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现的改进的模式匹配算法简称为KMP算法.大概学过信息学的都知道,是个比较难理解的算法,今天特把它搞个彻彻底底明明白白. 注意到这 ...

随机推荐

  1. C# 双向链表的实现

    类数据 public class Objects { private int number; /**//* 货物编号 */ private string name; /**//* 货物名称 */ pr ...

  2. IDE-常用插件

    2021-8-25_IDE-常用插件 1. 背景 提升编写代码的舒适度,提升开发效率 2. 常用插件列表 IDE EVal Reset 白嫖付费的golang编辑器,reset插件可以重置golang ...

  3. IL合集二

    引言 在第一篇关于IL的文章中,我们写了一些IL的相加,创建对象,循环以及实现TryCatch的一些功能,接下来,为大家带上后续关于IL的更新,其中包括,类型转换,以及条件判断,还有定义字段,定义属性 ...

  4. [转帖]修改Linux内核参数,减少TCP连接中的TIME-WAIT

    https://www.cnblogs.com/xiaoleiel/p/8340346.html 一台服务器CPU和内存资源额定有限的情况下,如何提高服务器的性能是作为系统运维的重要工作.要提高Lin ...

  5. [转帖]WinXP添加TLS1.1、TLS1.2支持

    现象 HTTPS服务在Win7及Win10能够正常打开,但是在XP下用IE浏览器却无法打开,XP下用第三方浏览器(我试了谷歌浏览器)却能正常打开.经过抓包分析,用IE浏览器是协商用的是TLS1而用第三 ...

  6. [转帖]Sql Server之旅——第六站 使用winHex利器加深理解数据页

    https://www.cnblogs.com/huangxincheng/p/4251770.html 这篇我来介绍一个winhex利器,这个工具网上有介绍,用途大着呢,可以用来玩数据修复,恢复删除 ...

  7. [转帖]@Scope("prototype")的正确用法——解决Bean的多例问题

    https://www.jianshu.com/p/54b0711a8ec8 1. 问题,Spring管理的某个Bean需要使用多例   在使用了Spring的web工程中,除非特殊情况,我们都会选择 ...

  8. Python学习之十二_tkinter的学习与使用

    Python学习之十二_tkinter的学习与使用 摘要 本来想说会用QT5进行界面编程 但是发现比较繁琐 还是先学习使用 tkinter的方式进行界面化的编写和学习了 基础知识 tkinter是一个 ...

  9. [转帖][大数据]ETL之增量数据抽取(CDC)

    https://www.cnblogs.com/johnnyzen/p/12781942.html 目录 1 CDC 概念 1.1 定义 1.2 需求背景 1.3 考察指标 2 CDC 常见解决方案 ...

  10. Windows 磁盘部分性能数据获取

    Windows 磁盘部分性能数据获取 摘要 每次晚上加班总有收获 这次发现了一个fio for windows版本的压测程序, 准备学习和使用一下. https://github.com/axboe/ ...