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

在一個字符串中查找一個子字符串有很多方法,最簡單容易想到的算法便窮舉,但這樣的情況下算法複雜度爲O(m * n)。
而KMP是一種最常見的改進算法,它可以在匹配過程中失配的情況下,有效地多往後面跳幾個字符,加快匹配速度。
KMP算法中有個數組,叫做前綴數組,也有的叫next數組,每一個子串有一個固定的next數組,它記錄着字符串匹配過程中失配情況下可以向前多跳幾個字符,當然它描述的也是子串的對稱程度,程度越高,值越大,當然之前可能出現再匹配的機會就越大。

先來個圖表來表示一下next數組的求解方式:
這裏寫圖片描述
可是如何去求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<iostream>
#include <string>
#include <map>
#include <vector>
#include <cmath>
#include <bitset>
#include <stack>
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;
}

測試結果:
這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章