当前位置: 首页 > news >正文

KMP-子串匹配算法-关键点理解

1.理解next[]数组的使用与来历

2.求解next[]数组

一、kmp算法的原理

        首先观察暴力解法:假设主串为:abdxxabc,模式串为abxxabd。

暴力解法,就是对主串每个字符作为第一个字符,开始和模式串比较。

比如:从主串第一个字符a开始,比较到d发现不相等,然后从第二个字符b开始,直接不相等。以此类推,直到匹配或者到主串末尾。

           abxabc|abxabd                       ​​​​​​​abxabc|abxabd     ...       abxabc|abxabd

           abxabd                                     abxabd                ...                    abxabd

             图一                                         图二                                       图三

比较操作的时间复杂度为O(m×n)。

kmp算法在此基础上进行改进,关键点为,利用已经比较过的部分,比如图一的abxabd。

        当遇见不匹配的情况时,通过模式串的最长相等前后缀来确定下一次模式串比较的位置,并且不需要回退主串和模式串。前缀为不包含最后一个字符,包含第一个字符的子串,后缀为不包含第一个字符,保护最后一个字符的字串。

        理解:匹配过程中,不匹配的情形只有两种情况,其实也可以看成一种情况。第一种情况:第一个字符就不匹配,也就是说不匹配的字符前面没有字符。如图二。第二种情况:第n个字符不匹配,前n-1个字符匹配。如图一。

       第一种情况由于第一个字符就不匹配,所以主串直接遍历下一个字符,模式串还是第一个字符开始比较。第二种情况:如图一,abxab是匹配的,此时,我们只需要检查abxab这个字符串的最长相等前后缀的长度,最长相等前后缀为ab,长度为2。下一次我们直接比较模式串下标为2的字符x和此时主串不相等的字符c。然后重复上面的比较操作。

        关键点是理解最长相等前后缀的性质。在已经匹配的字符串中,如何最长相等前后缀为0,也即是没有相等的前后缀,那么显然,由于已经匹配的字符串在模式串中为模式串的一个前缀,而在主串中为已经遍历过的匹配的字符串的后缀,假设从i开始比较,到j不相等,主串i ~ j-1这一部分是模式串的一个前缀,因为肯定是从模式串的第一个字符开始比较的。而i - j-1这一部分如果没有相等的前后缀,那么显然不可能有和模式串匹配的部分,因为i~j-1是模式串的一个前缀,你都没有后缀等于前缀,所以主串不用回退,直接遍历下一个字符;如果有相等的前后缀,我们取最长的相等前后缀,i~j-1是模式串的一个前缀,假设最长的相等前后缀长度为2,也就是i, i+1是等于j-2, j-1,所以模式串的下一次比较的位置就是i+2,因为i~j-1也是主串的一部分,主串下一次比较的开始字符是j。

2.关键的最长相等前后缀的求解,也就是常说的next的数组。也就是前缀表。

一、暴力求解:两个指针,left,right,分别指向后缀的开始,前缀的末尾。从最长的前后缀字串开始比较,逐渐缩短,直到不相等。

二、常用解法:kmp的思路,其实求解next数组的过程中也用到了kmp的算法思想。一个指针j,表示以i-1为尾部的前缀字符串的最长相等前后缀的长度,也即是j指向最长相等前缀的下一个字符。i遍历字符串,从0开始。显然,第一个前缀,最长相等前后缀长度为0。

比如字符串abcxabxabcxx。next[]数组:next[0]:前缀a的最长相等前后缀的长度;next[1]:前缀ab的最长相等前后缀的长度;next[2]:前缀abc的最长相等前后缀的长度,....表示以i结尾的前缀字符串的最长相等前后缀的长度。

for(i = 0; i < str.size(); ++i)遍历一个一个求next[i]数组。

if (str[i] == str[j])   next[i] = ++j;

abcxabxabcxx: 假设此时i遍历到了x,j为i-1结尾的前缀字符串的最长相等前后缀长度,也就是红色部分表示的前缀字符串的最长相等前后缀长度,为2,也即是j指向最长相等前缀的下一个字符c。

其实就是next[i]是和next[i-1]有关系的。

关键是str[i] != str[j], while(j > 0 && str[i] != str[j]) j = next[j-1];其实就是把字符串0~j看成模式串,

i-j~i看成主串,因为指针j,表示以i-1为尾部的前缀字符串的最长相等前后缀的长度,也即是j指向最长相等前缀的下一个字符。i-j~i-1是等于0-j-1的。

28. 找出字符串中第一个匹配项的下标 - 力扣(LeetCode)

参考代码:

class Solution {
public:
    int strStr(string haystack, string needle) {
        //第一部分:求解next[]数组
       vector<int> next(needle.size());
        int j = 0;
        for(int i = 1; i < needle.size(); ++i){
            while(j > 0 && needle[j] != needle[i]){
                j = next[j - 1];
            }
            if(needle[i] == needle[j]){
                j++;
            }
            next[i] = j;
        }
        //第二部分:匹配文本串
        j = 0;
        for(int i = 0; i < haystack.size(); ++i){
            while(j > 0 && needle[j] != haystack[i])
                j = next[j - 1];
            
            if(haystack[i] == needle[j]){
                j++;
            }
            if(j == needle.size())
                return i - j + 1;
        }

        return -1;

    }
};

相关文章:

  • 蓝桥杯 对联【算法赛】
  • 仙剑奇侠传1隐藏宝箱
  • 【小派项目书】sprintboot + vue 语言实现
  • 单机游戏的工作逻辑
  • Spring IoC DI入门
  • QT-LINUX-Bluetooth蓝牙开发
  • 解释 TypeScript 中的类型保护(type guards),如何使用类型保护进行类型检查?
  • 麦肯锡外运集团企业卓越采购供应链管理体系规划(117页PPT)(文末有下载方式)
  • 阿里云平台Vue项目打包发布
  • 识别并脱敏上传到deepseek/chatgpt的文本文件中的护照信息
  • 晶鑫股份迈向敏捷BI之路,永洪科技助力启程
  • 天梯赛 L2-011 玩转二叉树
  • 使用uniapp的vite版本进行微信小程序开发,在项目中使用mqtt连接、订阅、发布信息
  • 若依(RuoYi)框架新手使用指南
  • Bilve 搭建手册
  • L2TP的LAC拨号模式实验
  • 【SpringBoot】你不能不会的SpringBoot图形验证码生成
  • 自学Python创建强大AI:从入门到实现DeepSeek级别的AI
  • bootstrap介绍(前端框架)(提供超过40种可复用组件,从导航栏到轮播图,从卡片到弹窗)bootstrap框架
  • 1688商品数据实战:API搜索接口开发与供应链分析应用
  • 美国新泽西客运公司遭遇罢工:40年来首次,35万人受影响
  • 戛纳打破“疑罪从无”惯例,一法国男演员被拒之门外
  • 王东杰评《国家与学术》︱不“国”不“故”的“国学”
  • 通往国际舞台之路:清政府与万国公会的交往
  • 牛市早报|持续推进城市更新行动意见印发,证监会强化上市公司募资监管
  • 因存在安全隐患,福特公司召回约27.4万辆SUV