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)

字符串匹配

在常见的字符串相关的算法问题中有一种较为常见的是匹配问题,分为单模匹配与多模匹配,
多是要求我们在时限内计算单个或多个模式串在很长的一串原文串中的出现情况
单模匹配最好想到的就是时间复杂度O(n*m)的暴风(bf)算法,而当n和m是1e5量级的时候bf算法容易超时,
故而Knuth , Morris 和 Pratt 三人研究出了一种更加优秀的时间复杂度O(n+m)的算法去三人名称首字母
命名为KMP算法,自此单模匹配问题得到解决.
更难的多模匹配问题是通过AC自动机解决的,AC自动机简单的说就是利用字典树存储所有模式串,并使用KMP思
路在字典树上构造失败跳转指针,在匹配时将原文串与字典树进行模式匹配

字符串匹配暴力

void bf(const char*a,const char*b){
    int i=0,j,ans=0;
    while(a[i]){
        j = 0;
        while(b[j]&&a[i+j]==b[j])
            j++;
        if(!b[j])
            printf("原串%d下标位置匹配成功\n", i);
        i++;
    }
}

kmp算法

概念:
KMP是一种时间复杂度O(n+m)的字符串单模匹配算法,核心是通过预处理模式串的所有前缀串的最长相同前后缀,构造next数组,在原串与模式串匹配失败时参考next数组做跳转避免原串指针的回退并减少模式串指针的回退长度。
作用:
KMP算法的作用是在一个已知字符串中查找子串的位置,也叫做串的模式匹配。比如主串s=“goodgoogle”,子串t=“google”。
感谢博主 : https://blog.csdn.net/weixin_46007276/article/details/104372119
预备知识:
字符串 abcdab
前缀的集合:{a,ab,abc,abcd,abcda}
后缀的集合:{b,ab,dab,cdab,bcdab}
那么最长相等前后缀不就是ab嘛.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dvj0TTXT-1645427480886)(C:\Users\14996\AppData\Roaming\Typora\typora-user-images\image-20210818161441923.png)]

next[i]数组作用:
1.next[i]的值表示下标为i的字符前的字符串最长相等前后缀的长度。
2.表示该处字符不匹配时应该回溯到的字符的下标
匹配方式以及next[i]数组的作用含义:
在红色位置失配了,j 要重新匹配,那和模式串中哪个字符重新匹配呢?应该是已匹配部分的最长相同前后缀中前缀后一个字符,比如模式串abac当c失配时aba已匹配成功,最长相同前后缀是a,最长相同前缀后一个字符是b,所以原串失配字符下一次应该和b比较,因为我们的已匹配成功部分若想成为新的匹配成功情况的一部分必然是某后缀作为了模式串的前缀。
比如已匹配部分的最长相同前后缀长度若为x,则原串失配字符下一次就该与模式串第x+1个字符比对,而字符串0开始存的,第x+1个字符刚好在数组下标x的位置

在这里插入图片描述

next[i]数组的实现

实现逻辑:
next数组实现逻辑:
0下标对应的next值设置为某负数,因为next数组含义为最长相同前后缀长度,长度不可能为负数,故而实际上这是一个特殊值,因为若是连模式串的第一个字符都与失配位置没匹配上,则需要一个特殊值提醒原串指针后移模式串重新开始匹配。
然后从左到右推算每个位置的next值,用一个j记录当前[0,i)部分的最长相同前后缀长度,比较最长相同前缀后一个字符s[j]与新字符s[i]是否相同,相同则前后缀得到增长,否则j=next[j]比较次长相同前缀后一个字符是否与s[i]相同,直到j退为负数特殊值。将next值赋为0。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v2uUbN9H-1645427399586)(C:\Users\14996\AppData\Roaming\Typora\typora-user-images\image-20210818162710214.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GoyLGlau-1645427399587)(C:\Users\14996\AppData\Roaming\Typora\typora-user-images\image-20210819113644585.png)]

next[]数组的优化
这种情况出现的原因是当你的next数组回退之后,回退的字符与当前失配的模式串字符一样,那么这次匹配依旧会发生适配现象,所以在每次填入新的Next数组时,将其回退的字符与当前字符匹配一下,当字符一样的时候,再次跳转至上一个Next,
优化的作用是重新赋值next数组,.使匹配的速度更快
在这里插入图片描述

kmp算法实现

void getnext(char* s){
    int i=0,j=-1;
    next[i]=j;
    while(s[i]){//该条件为判断数组是否为空,可以根据题意改变
        if(j==-1||s[i]==s[j]){
            next[++i]=++j;
            if(s[i]==s[next[i]]){//优化这种情况出现的原因是当你的next数组回退之后,回退的
                next[i]==next[next[i]];//字符与当前失配的模式串字符一样,那么这次匹配依旧会发生适配现
            }//象,所以在每次填入新的Next数组时,将其回退的字符与当前
        }//字符匹配一下,当字符一样的时候,再次跳转至上一个Next
        else j=next[j];
    }
}
void kmp(char* a,char *b){
    getnext(b);
    int i=0,j=0,ans=0;
    while(a[i]){
        if(j==-1||a[i]==b[j]){
            i++,j++;
            if(!b[j]){//判断j加一后如果b数组到头了
                printf("原串%d位置匹配成功\n",i-strlen(b));
                ans++;//匹配成功的次数
                j=next[j];//跳回到上一个最长相同前后缀
            }
        }
        else j=next[j];
    }
}

kmp模板

next[i]数组作用:
1.next[i]的值表示下标为i的字符前的字符串最长相等前后缀的长度。
2.表示该处字符不匹配时应该回溯到的字符的下标
KMP字符串
给定一个模式串 S,以及一个模板串P,所有字符串中只包含大小写英文字母以及阿拉伯数字。
模板串 P 在模式串 S 中多次作为子串出现。
求出模板串 P 在模式串 S 中所有出现的位置的起始下标。
输入格式
第一行输入整数 N,表示字符串 P 的长度。
第二行输入字符串 P。
第三行输入整数 MM,表示字符串 S 的长度。
第四行输入字符串 S。
输出格式
共一行,输出所有出现位置的起始下标(下标从 0 开始计数),整数之间用空格隔开。
数据范围
1≤N≤105
1≤M≤106
输入样例:

3
aba
5
ababa

输出样例:

0 2

AC代码
** 数组从1开始存储 ,然后ne数组匹配答案串,ne[1]=0,然后ne数组中的i表示前i位可以匹配的相同前后缀,j的坐标每次都比i小1 **

#include <iostream>

using namespace std;
const int N = 100010, M = 1000010;

int n, m;
int ne[N];
char s[M], p[N];

int main(){
    cin >> n >> p + 1 >> m >> s + 1;//下标均从1开始

    for (int i = 2, j = 0; i <= n; i ++ ){ //j表示匹配成功的长度,i表示p数组中的下标,因为p数组的下标是从1开始的,只有1个时,一定为0,所以i从2开始
        while (j && p[i] != p[j + 1]) j = ne[j];
        //如果不行可以换到next数组
        if (p[i] == p[j + 1]) j ++ ;
        //成功了就加1
        ne[i] = j;//得到ne数组的值
        //对应其下标
    }

    for (int i = 1, j = 0; i <= m; i ++ ){
        while (j && s[i] != p[j + 1]) j = ne[j];
        if (s[i] == p[j + 1]) j ++ ;
        if (j == n){
            printf("%d ", i - n);//第一次匹配成功的位置 
            j = ne[j];//然后回跳,接着找 
        }
    }

    return 0;
}
  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-02-22 20:51:10  更:2022-02-22 20:51:54 
 
开发: 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/26 15:45:01-

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