IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 【数据结构】KMP算法详解 -> 正文阅读

[数据结构与算法]【数据结构】KMP算法详解

有这样一个问题:
给定一个字符串(模式串S):"BBC ABCDAB ABCDABCDABDE"
我想知道其中是否包含(模板串P)"ABCDABD" ?

1 两种字符串匹配思路

1.1 暴力思路

想象两把尺子,S是上面的长尺,P是下面的短尺,最开始两个尺子左端对齐,下面的短尺从左到右一位一位移动,直到匹配成功。
比如:当下面的尺子对齐了上面的 ABCDAB,但是 D 对应的是空格,这时匹配失败,只能往右再一位一位移动

1.2 KMP思路

Knuth、Morris、Pratt三个学者提出这样一种方法:就这个例子而言,当 D 对应的是空格时,我们并没有前功尽弃,因为我们已知 D 前面的 ABCDAB 是已经正确匹配的,同时我们发现正确匹配的字符串前后两端都有 ABABCDAB),那么我们可以直接向后移动 4 位,继续去匹配空格,观察能否匹配成功,即:

  • 初始
    BBC ABCDAB ABCDABCDABDE
    XXX ABCDABD
  • 向后移动4位
    BBC ABCDAB ABCDABCDABDE
    XXX XXXXABCDABD
  • 注:X代表检查过的

如果空格匹配成功则继续,匹配失败的话可以再观察是否有类似“回文”1的形式,如果有的话可以一次移动多位,如果没有只能一位一位移了。(实际上还剩下的AB已经不具备跳着移动的条件了,如果仍然无法匹配,只能全部移过去从头开始了)
1:“类似”的含义是:ABAB不是回文形式,ABABA是回文形式,两者都可以跳着走。

我们再给一个例子来感受KMP:
模式串S:ababaeaba
模板串P:ababacd

  • ababaeaba
    ababacd
    'c’无法与’e’匹配,观察到"ababa"的头是"aba",尾也是"aba",那么可以直接移2位
  • ababaeaba
    xxababacd
    'b’仍然无法与’e’匹配,观察到"aba"的头是"a",尾也是"a",那么可以直接移2位
  • ababaeaba
    xxxxababacd
    'b’无法与’e’匹配(紧跟亮色后面的),只剩a也不能跳着走了,这样就只能从e开始重新来了。

如果是暴力的话,第一次匹配不到e,可能就是这样的结果:(一位一位移动看是否能匹配)

  • ababaeaba
  • xababacd
  • xxababacd

2 概念补充

2.1 模式串和模板串

s[ ]是模式串,即比较长的字符串。
p[ ]是模板串,即比较短的字符串。(这样可能不严谨

2.2 前缀后缀

“非平凡前缀”:指除了最后一个字符以外,一个字符串的全部头部组合。
“非平凡后缀”:指除了第一个字符以外,一个字符串的全部尾部组合。
以下均简称为前/后缀。

举例:P = abcab,假设下标从1开始,分别对应1、2、3、4、5

下标前缀后缀
1
2{ a }{ b }
3{ a, ab }{ c, bc }
4{ a, ab, abc }{ a, ca, bca }
5{ a, ab, abc, abca }{ b, ab, cab, bcab }

2.3 next数组

在上面的例子中,我们每一次应该向右直接移动多少位是通过next数组得到的,而next数组是经过预处理得到的。
简单的来讲,next数组可以这样通俗地描述:
next[i]:以i为终点的后缀和从1开始的前缀相等,且长度最长。数组中存放的是这个前缀的最后一个元素的下标。(因为这样才能拼接过去)

// 伪代码形式
next[i] = j;
p[1, j] = p[i - j + 1, i]

还是刚刚那个:P = abcab

下标 i前缀后缀next[i]
10
2{ a }{ b }0
3{ a, ab }{ c, bc }0
4{ a, ab, abc }{ a, ca, bca }1
5{ a, ab, abc, abca }{ b, ab, cab, bcab }2

eg.对5来说,next[5] = 2的含义就是:p[1 ~ 2] = p[4 ~ 5]

3 代码实现

3.1 next数组的应用

有了next数组,我们每次匹配不成功时,就可以直接得出下一个跳向哪个位置,这一操作可以直接由j = next[j]来完成。
插句题外话,在y总眼里,这是 j 的回退,其实 j 每次回退,都是P串在跳跃行进匹配的过程。
假设我们已经有了next数组,那么kmp匹配的过程可以用代码表示如下:

int ne[N];	// next数组
// kmp 匹配过程
for(int i = 1, j = 0; i <= m; i ++){
	while(j && s[i] != p[j+1]) j = ne[j];	// 当j没有回退起点 且 当前的S[i]无法匹配时
	if(s[i] == p[j + 1]) j ++;
	if(j == n){
		/*
		匹配成功
		*/
		j = ne[j];	// 继续寻找下一个匹配串
	}
}

贴一张y总上课讲的抽象图:(配合上面代码)
在这里插入图片描述

3.2 next数组的初始化

先给一个例子,便于下面算法的模拟,顺便看看对next数组是否理解了:
P = ababa,则:(始终记住KMP算法中下标我们都从1开始

next[ i ]初始化依据
10
20
31a
42ab
53aba

next数组的初始化的本质就是自己和自己匹配
一定要想清楚在kmp匹配过程中,P字符串的 j 到底意味着什么:j 的作用就是下一次 P 字符串的开始匹配点(的前一个)。
通俗的讲,j 就是在P串中能匹配到哪,记录每一个能匹配到哪的位置就是next的初始化过程。
好难描述我脑海中的动图,希望以后的我能看懂……

  • 最开始
    j = 0 ababa
    i = 2 baba 无法匹配到,所以ne[2] = 0
  • i = 3 aba 匹配到了,j = 1,所以ne[3] = 1
  • i = 4 ba 匹配到了,j = 2,所以ne[4] = 2
  • i = 5 a 匹配到了, j = 3,所以ne[5] = 3
  • 如果匹配不到的话,也不用一步一步挪,直接用已经有的ne[j],匹配的会更快

代码写出来和3.1几乎一样,毕竟都是匹配的过程。

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 ++;
	ne[i] = j;
}

3.3 总代码

题目链接:KMP匹配字符串

#include<iostream>

using namespace std;

const int N = 100010;
const int M = 1000010;
char p[N], s[M];
int ne[N];
int main(){
    int m, n;
    cin >> n >> p + 1 >> m >> s + 1;    // 保证下标从1开始(优雅来自于细节啊)
    
    // 求next
    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 ++;
    	ne[i] = j;
    }
    
    // kmp匹配
    for(int i = 1, j = 0; i <= m; i ++){
    	while(j && s[i] != p[j+1]) j = ne[j];	// 当j没有回退起点 且 当前的S[i]无法匹配时
    	if(s[i] == p[j + 1]) j ++;
    	if(j == n){
    		printf("%d ", i - n);
    		j = ne[j];	// 继续寻找下一个匹配串
    	}
    }
    return 0;
}

4 参考

[1] 字符串匹配的KMP算法–前缀和后缀的详解
[2] AcWing 831. KMP字符串-题解

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2021-08-24 15:48:54  更:2021-08-24 15:48:58 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 22:25:08-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码