算法篇--狠狠拷打KMP算法(手把手教你next数组)

目录

前言

一、经典字符串匹配的例题

二、BF算法

2.KMP算法

2.1.next数组

总结


前言

        话不多说直接以题为例子进行讲解。本题来源自力扣


提示:以下是本篇文章正文内容,下面案例可供参考

一、经典字符串匹配的例题

二、BF算法

 BF的实现思路:

        现有两个字符串,str1与str2,将str1的字符依此与str2的字符相比较。若遇见两个不相同的字符,从str1字符的现有位置跳到下一个字符上在重新进行比较,直到比较结束。代码实现就是遍历两个字符串,进行上述规则的比较。双重for循环进行遍历,如果str1的剩余字符的个数与str2的个数相同,还没有比较成功就说明不存在相同的字符串了,因为这时候str1的字符个数比str2的个数还要小就没有比较的必要了。所以遍历str1的结束条件就是下标 <=(str1的符个数-str2的字符个数)。str2正常遍历,之后就是在str2中的判断,如果第一轮遍历没有相同的,那么str1的下标就要被str2的下标多了1,第二轮多了2,所以在进行判断的时候我们str的下标就是 ( i + j ), 这样每次循环str1的下标就会向前移动一位,if判断条件是两个字符不相等使用continue跳出内部循环,str1下标加一在重新判断,如果成功则输出 i 那就是它的初始下标。 

class Solution {
public:
    int strStr(string haystack, string needle) 
    {
        int n = haystack.size(), m = needle.size();
        for( int i  = 0; i <= (n - m); i++)
        {
            bool flag = true;
            for(int j = 0; j < m; j++)
            {
                if(haystack[i+j] != needle[j])
                {
                    flag = false;
                    continue;
                }
            }
            if(flag)
            {
                return i;
            }
        }
        return -1;
    }
};

2.KMP算法

        KMP算法:一种改进的字符串匹配算法,它与BF算法不同的是,它的主串是不回退的,一直向前不回退。

2.1.next数组

        KMP算法中的核心就是next数组。它用来储存子串某个位置匹配失败后回退的位置。

        在看的时候一定要一步一步看,不能跳着看,前后是有关联的,如果了解next数组的特性那么可以跳着看

        我们用next[i] = k来表示不同的下标对应的k值,以0下标开始,也就是这里的首元素到 i 下标找到匹配成功部分的两个相等的真子串,不包含本身。一般来说前两个k值的固定的 -1 与0 以第一组为例后看 a 前面 ab 没有 a 为0向后走。k是返回字串数组 i 的位置,i 即是子串数组下标也是next数组下标。

        这里对应

a   b  a        找以a开头以b结尾的两个相同的串的个数--没有

-1  0              这一串是next数组 

a   b  a  b        找以a开头以a结尾的两个相同的串个数-- a与a一个

-1  0  0  1

a   b  a  b  c        找以a开头以b结尾的两个相同的串个数-- ab与ab两个

-1  0  0  1  2

a   b  a  b  c  d      找以a开头以c结尾的两个相同的串个数-- 没有

-1  0  0  1  2  0

a  b  c  a  b  c  a  b        找以a开头以a结尾的两个相同的串个数-- a b c a与a b c a一共四个

-1 0  0  0  1  2  3       可以进行字符串的重合,但是不能从首元素重合

        这里我们发现一个特征,就是k的如果进行增加一定是 +1 的。next数组一定是以1 2 3 4这样的顺序增长的,不会是 1 3 5这样。而变小是不确定的。

 

        至此,我们学会了,如何寻找k中对应的next值 ,那么如何用代码实现呢?k是next数组的值,k代表返回的真子串的下标i 

        我们假设 next[ i ] = k成立 即p[i] == p[k]所以i要回退到k下标 那么从 p[0]~p[k-1] 就是 a b c 而 p[x] ~p[i - 1] 也是 a, b, c那么  p[0]~p[k-1]==p[x] ~p[i - 1]即p[0]~p[2]与p[x]~p[i-1]的真子串是相同的,我们前提是知道 i,k 所以 k-1-0==i-1-x得出x==i-k得出x为5符合下图。然后我们将x代入得出 p[0]~p[k-1]==p[i-k]~p[i-1] 这个公式的前提是 next[i] = k; 如果 p[0]~p[k]==p[i-k]~p[i]  那么对比上个公式那么 next[i+1]=k+1。

 

        但是如果 p[i] != p[k]这时候k=2  那么我们对k进行回退这时候k=0但还是 p[i] != p[k]继续回退这时候 k=0,并且p[i] == p[k]那么就满足上面的next[i + 1] = k+1;即j+1的位置k=1

        综上所述得出一个结论:已知next[i] = k,如果p[i] == p[k],那么next[i+1] = k+1,如果p[i] != p[k],回退k,使其满足p[i] == p[k],得出next[i+1] = k+1

        上述条件的前提是知道next[i] = k, 但当我们进行代码实现的时候,不知道 i 位置中k的值,所以现在的 i 相当于原先将的 i+1所以p[i-1] 与p[k]进行比较,所以当p[i-1] == p[k]的时候,next[i] = k+1。

 

        这时候对于next数组的了解就很明确了。下面就是代码的实现,注释写的非常清楚

class Solution {
public:
    int strStr(string haystack, string needle) 
    {
        int n = haystack.size(), m = needle.size(); //主串和字串的长度
        if(n == 0 || m > n)    // 当主串或字串为0,或者字串大于主串返回-1
        {
            return -1;
        }
        else if(m == 0)
        {
            return 0;
        }
        int* next = new int[m]; //  给next数组开辟空间
        set_next(needle, next, m);//进入函数得到next数组
        int i = 0, j = 0;
        while(i < n && j < m )  //遍历主串,使主串一种走,如果走到主串现在的位置到结尾位置的元素
        //等于字串的位置还没有匹配。那么就再向后面走字串元素会比与主串相比较的元素对,那么就不用向后走了。
        {
            if((j == -1) || (haystack[i] == needle[j]))
            {
                i++;
                j++;
            }
            else 
            {
                j = next[j];
            }
        }
        delete[] next;    //扩容一定要释放内存,防止内存泄漏 
        if(j >= m)
        {
            return i - j;
        }
        else
        {
            return -1;
        }
    }

private:
    void set_next(const string& needle, int * next, int m)//next数组
    {
        //如果字串有两个元素
        next[0] = -1;//第一个next数组为-1
        int k = -1; //起始元素
        int i = 1;  //  已经k元素的下一个元素坐标
        if(m > 1)//字串有三个及以上元素
        {
            next[1] = 0;
            k = 0;
            i = 2;
        }
        while(i < m)
        {
            if(k == -1 || needle[i - 1] == needle[ k])//这里求得就是 i 位置下k的值
            {
                next [i] = k + 1;
                i++;
                k++;
            }
            else //回退
            {
                k = next[k];
            }
        }
    }
};

总结

        KMP和核心就是next数组,学会next数组基本上学会了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值