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

这篇具有很好参考价值的文章主要介绍了[数据结构] 串与KMP算法详解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

写在前面

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

尽管新旧交替只是一个瞬间,在大家互祝新年快乐的瞬间,在时钟倒计时数到零的瞬间,在烟花在黑色幕布绽放的瞬间,在心底默默许下愿望的瞬间……跨入新的一年,并不意味了一切都会朝着更美好,也没有什么会从天而降,我们赋予了它这份意义,让它自然裹挟着新的爱与希望而来。
当我的视线跃过癸卯兔年,一路的海浪翻涌千叠阳关,才发现此间飘零无关风月,只是山海与风又如期周游了人间一趟。
《人民日报》说,人生这条路很长,未来星辰大海般璀璨,不必踌躇于过去的半亩方塘,这些所谓的遗憾,可能是一种成长,那些曾受过的伤,终会化作光照亮前方的路。
总有一天你会明白,真正治愈你的从来都不是时间,而是你心理的那份释怀与格局,只要你的内心不慌乱,连世界都难影响你。

串的定义和实现

串的定义

串(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算法详解

KMP算法

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

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

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

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

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

\('ababa'\)为例:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

KMP算法的原理

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

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

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

对算法的改进方法:

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

写成:\(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的进一步优化

这里也仅给出代码,后面再补吧,菜鸡博主痛得不行了文章来源地址https://www.toymoban.com/news/detail-825350.html

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算法详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • 数据结构--KMP算法

    模板: 例题:acwing--kmp字符串(831. KMP字符串 - AcWing题库) 给定一个字符串 S,以及一个模式串 P,所有字符串中只包含大小写英文字母以及阿拉伯数字。 模式串 P 在字符串 S 中多次作为子串出现。 求出模式串 P 在字符串 S 中所有出现的位置的起始下标。 输入格式 第一

    2024年02月11日
    浏览(9)
  • 【数据结构】朴素模式匹配 & KMP算法

    【数据结构】朴素模式匹配 & KMP算法

    🌈 自在飞花轻似梦,无边丝雨细如愁 🌈   🌟 正式开始学习数据结构啦~此专栏作为学习过程中的记录 🌟 子串的定位操作通常称为串的模式匹配,它求的是模式串在主串中的位置,而朴素模式匹配就是一种不断移动主串指针,每一次都和模式串依次进行比较的暴力求解方法

    2024年02月16日
    浏览(12)
  • 数据结构--字符串的KMP算法

    数据结构--字符串的KMP算法

    朴素模式匹配算法: 一旦发现当前这个子串中某个字符不匹配,就只能转而匹配下一个子串(从头开始) 但我们可以知道: 不匹配的字符之前,一定是和模式串一致的 color{red}不匹配的字符之前,一定是和模式串一致的 不匹配的字符之前,一定是和模式串一致的 我们可以利用

    2024年02月12日
    浏览(7)
  • 数据结构—串的详细解释(含KMP算法)

    数据结构—串的详细解释(含KMP算法)

    1.1串的定义 串:串是由零个或多个字符组成的有限序列,又叫字符串(其的存储结构包含顺序表存储、单链表存储的形式。) 一般记为s=\\\"a1a2a3....an\\\"(n=0),其中,s是串的名称,用双引号(也可以使用单引号)括起来的字符序列是串的值,注意引号不是串的内容。ai(i=i=n)可以是字母、

    2023年04月09日
    浏览(13)
  • 【夜深人静学数据结构与算法 | 第一篇】KMP算法

    【夜深人静学数据结构与算法 | 第一篇】KMP算法

    目录  前言:  KMP算法简介: 引入概念: 前缀后缀 前缀表: 简单例子: 暴力遍历: KMP算法:​  KMP算法难点: 总结: 本篇我们将详细的从理论层面介绍一下什么是KMP算法,相对应的力扣刷题专栏里也会有相对应的习题,欢迎各位前往阅读。           KMP算法是一种字符

    2024年02月08日
    浏览(19)
  • (C语言)数据结构算法-病毒感染检测(BF算法&&KMP算法)

    病毒感染检测: 医学研究者最近发现了某些新病毒,得知它们的DNA序列都是环状的。为了快速检测出患者是否感染了相应的病毒,研究者将患者的DNA和病毒的DNA均表示成一些字母组成的字符串序列,然后检测某种病毒DNA序列是否在患者的DNA序列中出现过,如果出现过,则此人

    2024年02月08日
    浏览(9)
  • 《数据结构》实验报告四:串的模式匹配(BF算法、KMP算法)

    《数据结构》实验报告四:串的模式匹配(BF算法、KMP算法)

    1、了解 串 的基本概念。 2、掌握 串的模式匹配算法 的实现 。 说明以下概念 1、模式匹配:          串的模式匹配就是 子串的定位运算 。          设有两个字符串 S 和 T ,S为 主串(正文串) ,T为 子串(模式串) 。在主串S中查找与模式串T相匹配的子串,若匹配成功,确定

    2024年02月01日
    浏览(11)
  • 【数据结构】字符串匹配|BF算法|KMP算法|next数组的优化

    【数据结构】字符串匹配|BF算法|KMP算法|next数组的优化

    字符串匹配算法是在实际工程中经常遇到的问题,也是各大公司笔试面试的常考题目,本文主要介绍BF算法(最好想到的算法,也最好实现)和KMP算法(最经典的) BF算法,即暴力(Brute Force)算法,是普通的模式匹配算法,BF算法的思想就是将目标S的第一个字符与模式串T的第一

    2024年02月04日
    浏览(9)
  • 探索数据结构:顺序串与链式串的深入理解

    探索数据结构:顺序串与链式串的深入理解

    ✨✨ 欢迎大家来到贝蒂大讲堂✨✨ 🎈🎈养成好习惯,先赞后看哦~🎈🎈 所属专栏:数据结构与算法 贝蒂的主页:Betty’s blog 串是一种特殊的 顺序表 ,即每一个元素都是单独一个 字符 。在C语言中我们学习的字符串便是串的一种,它在我们的数据搜索与文本编译中起着不

    2024年04月17日
    浏览(10)
  • 【数据结构】模式匹配之KMP算法与Bug日志—C/C++实现

    【数据结构】模式匹配之KMP算法与Bug日志—C/C++实现

    ​ 🌈 个人主页: Sarapines Programmer 🔥  系列专栏: 《数据结构奇遇记》 🔖墨香寄清辞: 墨痕寄壮志,星辰梦未满。 通幽径心凝意,剑指苍穹势如山。 目录 🌞1. 模式匹配的基本概念 🌞2. 模式匹配的解决办法 🎈2.1 暴力匹配(BF)算法 🎈2.2 KMP算法 🤖2.3 BUG记录_KMP算法 1

    2024年02月04日
    浏览(7)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包