算法学习(19):KMP算法

KMP算法

前言

KMP算法的原理是利用next数组来保留之前比较所获得的信息,以此优化并加速O(N2)的暴力对比的算法,它的流程与暴力对比的方法一致,都是从左到右依次对比,超过长度还没找出就返回false。网上关于KMP算法的详细描述很多,所以本文只做大致总结,博主推荐的完整思路以及证明见https://www.bilibili.com/video/BV13g41157hK?p=13&vd_source=77d06bb648c4cce91c6939baa0595bcd P13 01:34:45开始

next数组

作为KMP算法的灵魂,next数组中存放的数据是下标为i的位置前面i个字符(注意不包含第i位的字符)组成的字符串的最长前缀和最长后缀相同的长度(前缀和后缀的长度只能小于i)。
举例:
abbabbc
假设现在i = 6,前面6个字符是abbabb

长度 1 2 3 4 5
前缀
a
ab abb abba abbab
后缀
b
bb abb babb bbabb
是否相等

所以这时的next数组下标为i的位置应该存放3
人为规定next数组的下标为0的位置对应的值为-1,下标为1的位置对应的值为0。

KMP算法怎样利用next数组加速比对

算法流程:str1代表被匹配的较长的字符串,str2代表要去查找的较短的字符串。准备两个指针,str1和str2同时从0位置出发,比对当前位置的字符是否相等,如果相等,则两个指针同时往后走,如果不相等,则str1的指针不动,str2的指针跳转到当前位置对应的next数组位置的数值的位置,假设现在是i位置,即i=next[i],为什么是这样跳,详见前言中视频链接中 P13 01:48:10处。继续比较是否相同,相同就同时往后走,不同str2的指针就继续跳,当str2指针来到-1位置时(即跳到next[0])时,不能再往前跳了,即没有前缀和str1指针的位置的后缀匹配了,这时str1的指针往下走一位,重复上面的流程,直到str1的指针或者str2的指针越界。str2的指针越界就表示在str1中找到了相同的字符串,这时返回字符串第一个字符在str1中的位置。如果是str1的指针越界了,代表没找到,返回-1。

next数组怎么求

思路:假设现在是在i位置,比对next数组中的i-1位置的值在str2中对应位置的字符与i-1位置上的字符是否相等,即str2[next[i - 1]] 是否等于 str2[i - 1],如果相等,则next[i] = next[i - 1] + 1。如果不相等,则再比较str2[next[next[i - 1]]] 是否等于 str2[i-1],与上面KMP流程中的str2的指针往前跳的过程类似,这个也是通过next数组往前跳,在跳的过程中一直与str2[i - 1]位置的字符比较,如果相等,当前跳到的位置的下标+1就是next[i]的值,如果一直跳到小于0,即next数组中下标为0的位置,则next[i]的值为0。继续i+1位置的值的计算。此过程即为什么这么跳的原因详见前言中视频链接中 P13 02:50:20

KMP算法C++代码实现

vector<int> getNextArr(string str);

int KMP(string str1, string str2)
{
    if (str1.size() < str2.size() || str1.size() == 0 || str2.size() == 0)
    {
        return -1;
    }
    int i1 = 0;
    int i2 = 0;
    vector<int> next = getNextArr(str2);
    while (i1 < str1.size() && i2 < str2.size())
    {
        if (str1[i1] == str2[i2])
        {
            i1++;
            i2++;
        }
        else if (next[i2] == -1)
        {
            i1++;
        }
        else
        {
            i2 = next[i2];
        }
    }
    return i2 == str2.size() ? i1 - i2 : -1;    //如果i2越界,代表找到了相同的字符串,返回i1 - i2(即字符串开头在str1中的下标),否则就是没找到
}

vector<int> getNextArr(string str)
{
    vector<int> next;
    next.resize(str.size());
    next[0] = -1;      //将next数组下标为0的位置的值设置为-1,下标为1的值不用设置,因为vector数组resize方法默认用0填充
    int cn = 0;       //i - 1位置next数组的值,即哪个位置的字符与i - 1位置的字符比较
    int i = 2;       //当前字符位置
    while (i < str.size())
    {
        if (str[i - 1] == str[cn])
        {
            next[i++] = ++cn;
        }
        else if (cn > 0)
        {
            cn = next[cn];
        }
        else
        {
            next[i++] = 0;
        }
    }
    return next;
}

KMP算法的时间复杂度

O(N),具体证明思路见:前言中链接 P13 02:42:10

posted @ 2022-07-31 13:08  小肉包i  阅读(42)  评论(0)    收藏  举报