常用算法——模式匹配(KMP)

在一个字符串中查找一个子字符串有很多方法,最简单容易想到的算法便穷举,但这样的情况下算法复杂度为O(m * n)。
而KMP是一种最常见的改进算法,它可以在匹配过程中失配的情况下,有效地多往后面跳几个字符,加快匹配速度。
KMP算法中有个数组,叫做前缀数组,也有的叫next数组,每一个子串有一个固定的next数组,它记录着字符串匹配过程中失配情况下可以向前多跳几个字符,当然它描述的也是子串的对称程度,程度越高,值越大,当然之前可能出现再匹配的机会就越大。

先来个图表来表示一下next数组的求解方式:
常用算法——模式匹配(KMP)_第1张图片
可是如何去求next数组每个数组元素的值呢?
-1. 逐个查找对称串
比如i = 0时,串对应的字符是a,不存在对称,所以next[0] = 0;
同样的,i = 1, 2, 3时 ag, agc, agct均不存在对称,所以next[1…3] = 0;
而i = 4时,agcta串的前缀a和后缀a相等,长度为1,所以next[4] = 1;
再比如i = 6时,agctagc的前缀agc和后缀agc相等,长度为3,所以next[6] = 3;
而i = 7时,agctagca只有前缀a和后缀a相等,长度为1,所以next[7] = 1;
之后,便可以次类推。
既然已经可以人工的推出这个next数组的值了,那么编程应该也可实现了:
编程思想如下:
1) 当前面字符的前一个字符的next值为0时,只要将当前字符与子串第一个字符进行比较。前面都是0,说明都不对称了,到当前位置时只是多加了一个字符,要对称的话最多是当前的和第一个对称。此时,如果当前字符和第一个相等,那个该位置的next值为1,否则为0;

2) 依此,可以总结一个规律,不仅当前一个字符的next值为0时,如果前面一个字符的next值是1,那么我们就把当前字符与子串第二个字符进行比较,因为前面的是1,说明前面的字符已经和第一个相等了,如果这个又与第二个相等了,说明next就是2了;

3) 同2)中所说如果一直相等,就一直累加。

说到这里,上面情况理解起来应该没什么问题,现在的问题是如果遇到下一个不等,该怎么进行处理?
比如i = 14时,该位置的前一个字符的next值为7,但该位置的next值按照最大公共前后缀(agct)的规则来看,长度为4,其next的值为4。
在这里相当于重新寻找i = 14,更小的对称性,那么:
1)如果要存在对称性,那么对称程度肯定比前一个字符 的对称程度小,所以要找个更小的对称;
2)要找更小的对称,必然在对称内部还存在子对称,而且当前字符还必须紧接着在子对称串之后;
所以,agctagc 和 agctagc对称,agc、agc、agc、agc、对称。

算法实现与测试:

#include
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
//next数组
void create_next(const string &seq,int *next)
{
    int k;//最大前后缀长度//
    int len = seq.size();
    //字符串的第一个字符的最大前后缀长度为0
    next[0] = 0;
    for (int i = 1, k = 0; i < len; ++ i)
    {
        //求出seq[0]···seq[q]的最大的相同的前后缀长度k
        while(k > 0 && seq[i] != seq[k])
            k = next[k-1];     
        //如果相等,那么最大相同前后缀长度加1
        if (seq[i] == seq[k])
        {
            k++;
        }
        next[i] = k;
    }
}
//KMP算法实现
int kmp(const string &str,const string &sub,int *next)
{
    //当前已经匹配的串长度//
    int q;
    int n = str.size();
    int m = sub.size();
    create_next(sub,next);
    int i;
    for (i = 0, q = 0; i < n; ++ i)
    {
        while(q > 0 && sub[q] != str[i])
            q = next[q-1];
        if (sub[q] == str[i])
        {
            q++;
        }
        if (q == m)
        {
            break;
        }
    }    
    return i - m + 1;
}
int main() 
{
    string str, sub;
    cin >> str >> sub;
    int *next = new int[sub.size() + 1];
    cout << "start position : " << kmp(str, sub, next) << endl;
    system("pause");
    return 0;
}

测试结果:
常用算法——模式匹配(KMP)_第2张图片

你可能感兴趣的:(常用算法,C/C++)