首页 > 编程语言 >KMP算法

KMP算法

时间:2023-01-03 17:35:10浏览次数:65  
标签:子串 主串 匹配 后缀 next 算法 KMP now

KMP算法

参考:如何更好地理解和掌握 KMP 算法?

模式串匹配算法,在一个主串(文本串s)中查找子串(模式串p)第一次匹配的位置

算法两个关键操作

  • 根据模式串建立next数组
  • 根据next数组进行子串匹配

1 Brute-Force算法(暴力求解)

两层for循环,遍历主串,从当前位置出发检查是否能与子串完全匹配。

如果匹配失败,检查主串下一个位置开始是否与子串匹配。

时间复杂度O(m*n)

没有利用到上一次匹配失败的信息以提升算法效率

2 KMP算法理解

Key Point:当匹配失败时,匹配失败位置之前的部分主串和子串是匹配的,利用这个信息,跳过中间不可能匹配成功的若干情况,以提升算法效率。

如果用上这个信息?

若已经匹配的部分有相同的前后缀,可以直接把子串移动多个位置,主串不动,使得子串的前缀和主串刚刚已经匹配部分的后缀相对应,继续比较主串和子串。

若相同的前后缀长度为已匹配部分的最大值,即最大相同前后缀,此时,最大限度的跳过了中间不可能匹配成功的情况,同时不会漏掉可能匹配成功的情况。

next数组:next[i]为模式串0~i最长相同前后缀长度(下标从0开始)

3 求next数组

难理解

初始next[0] = 0,串只有一个字符时,没有前缀后后缀,故最大相同前后缀长度为0

如果next[0], next[1], ... next[x-1]均已知,那么如何求出 next[x] 呢?

首先已经知道了next[x-1],即x之前的模式串的最大相同前后缀长度,设now = next[x-1]

则now即为最长相同前缀之后的一个字符,x为最长相等后缀之后的一个字符

  • 若p[now] = p[x]

    此时是最佳的情况,即最长相等前后缀在next[x-1]的基础上增长1位,next[x] = now + 1

    如下图所示:

    image-20230103152645708

  • 若p[now] != p[x]

    此时不是最佳情况,要检查次优情况,缩短当前最长相同前后缀长度

    注意到此时now-1=2,说明子串A最长相同前后缀长度为2,ab,子串A=子串B

    故子串A前缀ab,与子串B后缀ab为次优最长前后缀长度,更新now,再检查p[now]==p[x]

    如下图所示:

    img

实现代码:


void buildNext(int *next, char *p, int lenp){
    next[0] = 0;    //初始next[0] = 0
    int now = 0;    //now = x-1
    int x = 1;      //初始x = 1
    while(x < lenp){    //递推求next[x]
        if(p[x] == p[now]){     //p[x]=p[now]则在原来最大前后缀长度的基础上加1
            next[x++] = ++now;
//            等价于
//            now++;
//            next[x] = now;
//            x++;
        }
        else{   //p[x]!=p[now]
            if(now == 0){   //now=0,不能再缩小,next[x] = 0
                next[x++] = 0;
            }
            else{   //缩小now
                now = next[now-1];
            }
        }
    }
}

4 KMP算法实现

构建好next数组之后,使用next数组进行匹配的过程中,根据失配位置和next数组的指示调整模式串指针,主串指针不回退。

匹配过程中的所有可能情况如下:

  • 当前主串指针和模式串指针指向的字符相同(当前字符匹配成功)

    两个指针均后移一位

  • 当前字符匹配不成功

    • 模式串第一位就匹配失败(p[0]适配)

      模式串指针不动,主串指针后移一位

    • 除p[0]之外的失配,模式串指针j(j != 0)

      主串指针不动,模式串指针设为next[j-1]

图示理解如下

image-20230103164218065

实现代码

int kmp(char *s, int lens, char *p, int lenp){
    int *next = (int *)malloc(sizeof(int) * lenp);
    buildNext(next, p, lenp);
    int i = 0;	//主串指针
    int j = 0;	//模式串指针
    while(i < lens){	//遍历主串
        if(s[i] == p[j]){	//当前字符匹配成功
            i++;
            j++;
        }
        else{  //失配
            if(j == 0){		//p[0]失配,子串指针不动,主串指针后移1
                i++;
            }
            else{		//非p[0]失配,根据next数组调整模式串指针
                j = next[j-1];
            }
        }
        
        if(j == lenp){	//成功匹配模式串
            return i - j;	//返回主串中匹配成功的起点
        }
    }
    return -1;	//匹配失败
}

标签:子串,主串,匹配,后缀,next,算法,KMP,now
From: https://www.cnblogs.com/dctwan/p/17022916.html

相关文章

  • 算法竞赛进阶指南 0x43 线段树
    文章目录​​线段树简介​​​​线段树的简单代码实现​​​​建树代码​​​​修改操作​​​​查询操作​​​​线段树的查询操作的时间复杂度分析:​​​​[AcWing245.你......
  • 美颜sdk磨皮算法与人脸皮肤识别技术
    在之前的文章中小编曾提起过,美颜sdk以及其它美颜工具的核心技术都是人脸关键点识别,只有先识别人脸关键点才能进行后续的美颜操作。今天小编要讲的美颜sdk磨皮算法同样不例外......
  • 小程序 SHA1加密算法使用
    创建一个js文件,或写入util.js中//SHA1加密functionencodeUTF8(s){vari,r=[],c,x;for(i=0;i<s.length;i++)if((c=s.charCodeAt(i))<0x80)r.pu......
  • 排序算法
    选择排序选择排序(Selectionsort)是一种简单直观的排序算法。它的工作原理如下。首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续......
  • 每日算法之把二叉树打印成多行
    JZ78把二叉树打印成多行题目给定一个节点数为n二叉树,要求从上到下按层打印二叉树的val值,同一层结点从左至右输出,每一层输出一行,将输出的结果存放到一个二维数组中......
  • 工程师应该学点算法——图论2
    为什么QQ要给女朋友推送前女友?这还是从图的算法说起。前篇->​​图论1​​图的遍历在图的遍历中我们一定要掌握两种最基础的算法:深度优先和广度优先。深度优先遍历(DFS)这......
  • 算法刷题 Day 6 | 242.有效的字母异位词 349. 两个数组的交集 202. 快乐数 1. 两数之
    哈希表理论基础建议:大家要了解哈希表的内部实现原理,哈希函数,哈希碰撞,以及常见哈希表的区别,数组,set和map。什么时候想到用哈希法,当我们遇到了要快速判断一个元素是......
  • 算法基础之全排列(递归)
    全排列(递归)全排列就是从第一个数字起每个数字分别与他后面的数进行交换。去重的全排列就是从第一个数起每个数分别与它后面非重复出现的数字交换。全排列(不去重)//搜索/......
  • 【Leetcode】天堂硅谷·数字经济算法编程大赛(虚拟)
    感受题目清单​​​https://leetcode.cn/contest/hhrc2022/​​周末比较忙,两场比赛都没有参加,打的虚拟赛。题解A.化学反应实验室内有一些化学反应物,其中的任意两种反应物......
  • 牛客寒假算法基础集训营4-J-Applese 的减肥计划
    链接:​​https://ac.nowcoder.com/acm/contest/330/J​​牛客网 已知Applese两只手分别产生的力的大小,以及它们之间的夹角,试求两力合力的大小。输入描述:仅一行三个整......