【算法与数据结构】KMP算法讲解(C++与python实现)

KMP算法讲解(C++与python实现)

KMP算法讲解

全称:Knuth-Morris-Pratt算法

KMP算法用于在一个文本串中高效地查找一个模式串的出现位置,它利用了模式串内部的重复性质,在匹配失败时避免不必要的比较,从而提高了匹配效率

步骤及具体操作:

  1. 首先,我们要求出一个PM数组
  2. 利用PM数组获取next数组:next[] = [0]+PM[0:-1]
  3. 遍历主串和模式串:
    1. 如果两串其位置元素相等,主串位置++,模式串位置++
    2. 如果位置不等,模式串利用next数组回溯,直到相等或回到初始位,主串位置++
    3. 当模式串遍历完,代表找到匹配位置
    4. 若模式串未遍历完,主串剩余位置不足以匹配,则在主串中不存在。

PM数组

PM 数组:也称部分匹配表或失配函数,用于 KMP 算法中,在匹配失败时决定模式串向右移动的位置。

如何获取PM数组(从字符串表意分析):
  1. 首先我们明确前后缀的含义,前缀指除去最后一个字符的字符串,后缀指除去最前面一个字符的字符转,如:abab的前缀是aba,后缀是bab。
  2. 那么前后缀的子串又是什么呢?前缀从前往后,后缀从后往前。即aba的子串:a,ab,aba,后缀bab的子串:b,ab,bab
  3. 而PM数组就是在字符串该位置上,其前后缀最大公共子串的长度, 那么abab这个位置上就应该是2,因为最大公共子串是ab
  4. 示例:ababa的PM数组就为[0,0,1,2,3]。
采用代码的形式获取PM数组:
  1. 我们使用j, i两个指针,i指针不回溯,j指针回溯
  2. 在求PM数组的过程中,我们同样要使用动态规划(匹配模式)的思想
  3. i指针指向1, j指针指向0
  4. 当str[i]== str[j] 匹配,i++, j++, PM[i] == j+1 (因为数组下标从0开始);
  5. 当str[i]!= str[j] 不匹配:
    1. 如果j > 0 ,利用已求得的PM数组回溯j指针j = PM[j-1],直到j回到起始位,或者str[i]== str[j] ,则PM[i] = j +1, j++ ; 这一步的思想在于:此时的j前面已经包含部分子串和i当前位置匹配,所以我们不用一一进行判断匹配
    2. 最后i++
  6. 当我们i走到最后一个字符,数组获取完成。

Cpp实现

void getNextArray(string pattern, vector<int> &nextVal, int strLen)
    {
        int i = 1, j = 0;
        while (i < strLen)
        {
            if (pattern[i] == pattern[j])
            {
                nextVal[i] = j + 1;
                j++;
            }
            else
            {
                while (j > 0)
                {
                    j = nextVal[j - 1];
                    if (pattern[i] == pattern[j])
                    {
                        nextVal[i] = j + 1;
                        j++;
                        break;
                    }
                }
            }
            i++;
        }
        nextVal.pop_back();
        nextVal.insert(nextVal.begin(), -1);
    }

next 数组

  • 一个字符串的最长公共匹配值,前缀(除去最后一个字符)和后缀(除去第一个字符)相等的最长子串

  • 如abaa的前缀aba 的子串有a、ab、aba,后缀baa的子串:a、aa、baa。

    很明显,两者没有相等的子串,所以为0。

  • 字符串 “ababa” 对应的 next 数组为:[-1, 0, 0, 1, 2]。

一些补充说明

  • next数组是模式串的PM数组右移一位以后的数组(默认初始位为0)
  • PM数组是:前后缀最大相同数
  • 利用代码寻找PM数组就是,设置一前一后两个指针
    • j为初始位0,i是当前位置
    • s[i]==s[j] n[i] == j+1 i++,j++
    • s[i]!=s[j]
      • j = n[j-1] 直到 s[j] == s[i] 然后同上
      • 找到j = 0仍无法等于,n[i] = 0,j不变,i 后移
  • KMP算法:
    • 回溯的是子串,失配时根据next数组不断回溯,直到回无可回
    • 主串永远向后移动,子串匹配或者回无可回就右移
    • 时间复杂度为O(m+n),m,n分别为两字符串的长度
    • 空间复杂度为O(m),构建了next数组

Leetcode实战演习

基础题例:Leetcode 28. 实现strStr()

python实现
  • 代码(kmp算法代码):
class Solution:
    def strStr(self, haystack: str, needle: str) -> int:
        '''
        使用KMP算法实现
        获取子串的next数组,
        当位置失配时,就跳转到数组指定位置重新开始匹配
        当第一位都第一位都失配,next[0] = -1
        就移动主串的指针,重新开始匹配
        '''
        if not needle:
            return 0
        next = self.getNext(needle)
        #主串起点,begin, 子串指针s son
        b ,s = 0,0
        while len(haystack) - b >= len(needle) - s:
            #判断是否匹配,否则移动
            while haystack[b] != needle[s] and s >= 0:
                s = next[s]
            #移动到匹配的地方了,匹配然后+1
            if haystack[b] == needle[s]:
                s += 1
            # 没有匹配上,回到起点
            else:
                s = 0
            if s == len(needle):
                return b - len(needle) + 1
            b += 1
        return -1
  • 代码2(next数组的寻找)
    def getNext(self,s):
        next = [0]*len(s)
        #设置右左指针
        i ,j = 1, 0
        while i < len(s):
            if s[i] == s[j]:
                next[i] = j + 1
                i += 1
                j += 1
            else:
                while j > 0 :
                    j = next[j-1]
                    if s[i] == s[j]:
                        next[i] = j + 1
                        j += 1
                        break
                i += 1

        #现在只是PM数组,现在要右移一位
        next = [-1] + next[:-1]
        return next
C++实现
class Solution
{
public:
    int strStr(string haystack, string needle)
    {
        // 使用KMP算法来解
        if (needle.size() == 0)
        {
            return -1;
        }
        int ndSize = needle.size();
        vector<int> nextVal(ndSize, 0);
        // 获得nextval数组
        getNextArray(needle, nextVal, ndSize);

        // 初始化遍历位置。
        int hsPos = 0, ndPos = 0;
        int hsSize = haystack.size();
        while (hsSize - hsPos >= ndSize - ndPos && ndPos < ndSize)
        {
            if (haystack[hsPos] == needle[ndPos])
            {
                ndPos++;
            }
            else
            {
                while (ndPos >= 0)
                {
                    if (haystack[hsPos] == needle[ndPos])
                    {
                        ndPos++;
                        break;
                    }
                    else
                    {
                        ndPos = nextVal[ndPos];
                    }
                }
                if (ndPos < 0)
                {
                    ndPos = 0;
                }
            }
            hsPos++;
        }
        if (ndPos == ndSize)
        {
            return hsPos - ndSize;
        }
        return -1;
    }

    void getNextArray(string pattern, vector<int> &nextVal, int strLen)
    {
        int i = 1, j = 0;
        while (i < strLen)
        {
            if (pattern[i] == pattern[j])
            {
                nextVal[i] = j + 1;
                j++;
            }
            else
            {
                while (j > 0)
                {
                    j = nextVal[j - 1];
                    if (pattern[i] == pattern[j])
                    {
                        nextVal[i] = j + 1;
                        j++;
                        break;
                    }
                }
            }
            i++;
        }
        nextVal.pop_back();
        nextVal.insert(nextVal.begin(), -1);
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值