字符串匹配算法(KMP)

兩個字符串A、B,在A字符串中查找B字符串(分別長為m,n),如果找到了,返回B字符串在A字符串中第一次出現(xiàn)的下標。

暴力的方法是在A[k],從k=0開始對比B字符串匯中的字符,如果失敗了,那么k++,時間復(fù)雜度為mn
這種算法忽略的B字符串中特性,也就是說,只向后移動一位對就開始對比,實際中,可能需要移動的位數(shù)更多。

所以,需要先找一下規(guī)律,當(dāng)在A[k]出對比B字符串失敗后,應(yīng)該向后移動多少位(也就是A[k+?]),開始對比?

引入一個概念:字符串前后綴最大相同長度
也就是一個字符串,如果前面n位和后面n位字符完全相同(順序一樣,不是鏡像)。那么n就是字符串前后綴最大相同長度(名字不要在意,別的都是公共部分,我覺得別扭)。假設(shè)該字符串長為m,那么n的取值范圍為0~m,也就是說這個前綴和后綴可以重疊,可以完全重疊。

假若,在A[k]中匹配B,在B[h]開始不同,那么直接向后移動B字符串前h位的前后綴最大長度(以后都稱為next[h])既可。
當(dāng)在B[h]匹配失敗后,B[h-next[k]]B[h]的字符串(前綴)與B[0]B[next[k]]的字符串(后綴)是相同的,在A中對應(yīng)的位置也是相同的。此時,只需要將B[0]移動到B[h-next[h]]位置,移動后,B在A中的前next[h]位是已經(jīng)匹配好的。所以不需要再從B[0]開始匹配了。

為什么可以跳過這么多的位置,重要的要理解next[]的構(gòu)造過程。

構(gòu)造過程之這樣子的:
next從下標1開始存儲,因為B從開始到結(jié)束,子字符串長度為0沒意義。當(dāng)子字符串長度為1是,next[1]=1

假設(shè)當(dāng)子字符串長度為k時(也就是B[k-1]以及之前),前后綴最大相同長度為m

子字符串長度為k+1:當(dāng)B[k]=B[m+1]時 ,(B[k]為之前的后綴之后的第一個字符,B[m+1]是前綴之后的第一個字符,如果相等,就是前后綴最大相同長度+1),所以next[k+1]=next[k-1]+1

不想等時:開始比較B[m]位和B[k]位,如果相等也就是,之前的后綴和前綴,在后面有公共部分。其相同長度可能為next[m]+1,因為B[m-1]子字符串的前綴的后一位可能不等于B[m]。如果相等,那么next[k]=next[next[k]]+1
否則,再比較B[m-1]B[k],同上。

#include <vector>
#include <iostream>
#include <string>

using namespace std;


vector<int> calNext(const string &str)
{
    int len = str.size();                                   //總長度
    vector<int> next(len + 1);
    next[2] = 0;
    int sublen;                                             //要計算next的子字符串
    for(int i = 2; i < len ; i++)  
    {
        sublen = i+1;
        if(str[i] == str[next[sublen-1]] )                  //sublen - 1 上一個子字符串長度
        {                                                   //next[sublen - 1] 上個子字符串前綴的后一位
            next[sublen] = next[sublen-1] +1;
        }else{
        
        //B子字符串前綴右移,以此比較其前綴與str[i]
        for(int j = next[sublen-1] -1  ; j >= 0; j--)         
        {
            //比較子字符串前綴與str[i]
            if(str[next[j]] == str[i])
            {
                //如果子字符串前綴后一位與str[i]相同
                //那么比較以前綴為子字符串的前綴的后一位與str[i]
                //這樣就還是比較了一個子字符串的前綴和后綴與待計算next的字符
                if(str[next[next[j]-1]] == str[i])
                //上面的-1,是將長度轉(zhuǎn)化為下標,next中的都是下標
                {
                    next[i+1] = next[next[j]]+1;
                    break;
                }
            }
        }
        }
    }
return next;
}

int main()
{
    string str("ABCKACBACLABKJ");
    str="abaabcaba";
    vector<int> next = calNext(str);

    for(int i = 1 ;i < next.size();i++)
    {
        cout<<next[i]<<" ";
    }
    cout<<endl;
}

這就是next的構(gòu)建,其基本思想是:
比較一個子字符串的前綴后一位和后綴后一位與待計算next字符,如果一樣,那么待計算字符的next值就是子字符串的next值+1.
現(xiàn)在next中存儲的就是每次跳轉(zhuǎn)的時候額外跳轉(zhuǎn)的歩數(shù),同時也是跳轉(zhuǎn)以后搜索的起點。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容