C++ KMP算法

C++KMP算法

前言

最近刷题发现对KMP算法理解实在是不到位,所以查了很多大佬们写的资料,总结整理了一下,打算自己也写一篇捋清一下思路,有不对的地方希望各位大佬指正

适用问题

两个字符串 判断其中一个是不是另一个字符串的子串,是就返回包含的起始位置

用算法的原因

原始的暴力破解法时间复杂度太高 为了简化时间复杂度

具体步骤

(设两个字符串判断ptr是不是str的子串):
1.计算ptr的Next数组
(存在字符串p!=目标字符串且p同时是字符串的前缀和后缀,
若存在p则next数组的值为前缀p的最后一个索引,若不存在数组值为-1)
(最长前缀不能是它本身,既从第一个字符开始,但是不包含最后一个字符)
eg:ababa ( 由于上述注意所说,只有一个字符的字符串不存在前缀 所以next[0]一定是-1
next[1]是ababa的ab 无子串p满足同时为ababa的前缀和后缀,所以next[1]=-1
next[2]是aba 存在子串a满足条件 值为前缀最后一个索引 next[2]=0
后面同理 一直到 next[4] .)
2 .初始化k=-1表示str已经被匹配到最后一位
3. 让i遍历ptr 每次试图比较ptr[k + 1] == str[i]
4.若不等,不断令k = next[k];直到k==-1 或ptr[k + 1] == str[i]
5.如果ptr[k + 1] == str[i]则k++ 直到k到达ptr.length()-1,

主要原理

原来的暴力算法 一个一个比较每次移动距离是1
KMP算法可以通过细分情况把移动距离加大减少比较次数 优化时间

代码转自: https://blog.csdn.net/starstar1992/article/details/54913261.

代码

// KMP
void cal_next(char *str, int *next, int len)//(主要思路是递推)
{
    next[0] = -1;//next[0]初始化为-1,所有字符串的next[0]均为1(不存在前缀)
    int k = -1;//k初始化为-1 k为相同前缀和后缀的最长长度 
    for (int q = 1; q <= len-1; q++) //循环 计算next[0]到next[q]的值
    {
        while (k > -1 && str[k + 1] != str[q])//如果下一个不同,那么k就变成next[k],注意next[k]是小于k的,无论k取任何值。
        {
            k = next[k];//往前回溯
        }
        if (str[k + 1] == str[q])//如果相同,k++
        {
            k = k + 1;
        }
        next[q] = k;//这个是把算的k的值(就是相同的最大前缀和最大后缀长)赋给next[q]
    }
}

代码理解

递推思想,假设已经求出了next[0]到next[q-1],下一步要求next[q];eg:字符串s=abababc

假设已经求出了next[0]=-1,next[1]=-1,next[2]=0,next[3]=1,求next[4].

当求到next[3],我们到abab,知道了最长的前后缀相等的子串p为ab
若s[4]==s[next[3]+1] 那p就可以向后多增加一位 既p=aba。 相比于s=abab新增了一个字母a

字符串变长了,则尝试将子串p也变长,之前的已经比较,只需比较s新增的字符与p变长新增的字符是否相等就行。
所以next[4]=next[3]+1=2

ok,接着计算next[5] 同理,让我们先看看s[5]是否等于s[next[4]+1],ababab 相等next[5]=next[4]+1=3

next[6]的情况就有些不同,我们发现s[6]!=s[next[5]+1] 即p不能像上面那样顺利变长了,那我们就尝试缩短

这时候一般的方法,是将K慢慢缩小,即k–;直到找到一个k满足str[k+1]==str[q]

但是这样容易遇到一个问题!

eg:abccabcb,str[7]匹配不到的时候会和str[5]匹配,next[7]变为0,实则应该是-1

KMP算法给了一种更正确的方法,k=next[k];即如果找不到合适的后缀,就跳过中间那段,去前缀里面找

举个例子:abababc 求到next[5]时p为abab p不能加长那就把p缩短 虽然ababa不等于ababc 但是我们已经比较了前面的abab相同,所以我们只需要把新增的后缀尾巴c 放到前缀里找就ok,那我们就只需要求abab的p就好了,即next[3]

即看一眼k=next[3],比较s[k+1]的值是否等于s[q]就好,就这样一直回溯,知道k=-1,找不到p。

KMP算法就同理了,直接上代码


int KMP(char *str, int slen, char *ptr, int plen)
{
    int *next = new int[plen];
    cal_next(ptr, next, plen);//计算ptr的next数组
    int k = -1;
    for (int i = 0; i < slen; i++)
    {
        while (k >-1&& ptr[k + 1] != str[i])//ptr和str不匹配,且k>-1(表示ptr和str有部分匹配)
            k = next[k];//往前回溯
        if (ptr[k + 1] == str[i])
            k = k + 1;
        if (k == plen-1)//说明k移动到ptr的最末端
        {
            //cout << "在位置" << i-plen+1<< endl;
            //k = -1;//重新初始化,寻找下一个
            //i = i - plen + 1;//i定位到该位置,外层for循环i++可以继续找下一个(这里默认存在两个匹配字符串可以部分重叠)
            return i-plen+1;//返回相应的位置
        }
    }
    return -1;  
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值