1. 程式人生 > >coding A&D:KMP演算法

coding A&D:KMP演算法

一些演算法、資料結構長時間不用,就忘記了,甚至連概念都忘了,最麻煩的是忘記當初如何理解該概念的方法了。。。

瀏覽部落格時發現這兩位博主的博文有助於我重新理解kmp演算法,遂參考一下,進行總結,有助於以後使用:

總結如下:

【概念】:首先KMP是用來進行串的模式匹配的演算法之一。串的模式匹配:給你兩個字串,尋找其中一個字串是否包含另一個字串,如果包含,返回包含的起始位置。

KMP的改進在於:每當一趟匹配過程中出現自負比較不等時(失配位),不許回溯i指標,而是利用已經得到的“部分匹配”的結果將模式串向右“滑動”儘可能遠的一段距離後,繼續進行比較。

例如:

這裡寫圖片描述

【時間複雜度】:KMP演算法可以實現複雜度為:O(m+n)

【注意】:用next陣列,next陣列是以下標0開始的!

【匹配過程】:

假設在我們的匹配過程中出現了這一種情況:  

藍色是匹配成功的部分,紅色是失配位:即匹配失敗了

根據KMP演算法,在該失配位會呼叫該位的next陣列的值!在這裡有必要來說一下next陣列的作用!說的太繁瑣怕你聽不懂,用一句話來說明:返回失配位之前的最長公共前後綴!

不管你懂不懂這句話,下面的文字和圖應該會讓你懂這句話的意思以及作用的! 首先,我們取之前已經匹配的部分(即藍色的那部分!):

我們在上面說到next陣列的作用時,說到“最長公共前後綴”,體現到圖中就是這個樣子:

接下來,就是最重要的了:

沒錯,這個就是next陣列的作用了: 返回當前的最長公共前後綴長度,假設為len。 因為陣列是由0開始的,所以next陣列讓第len位與主串匹配就是拿最長字首之後的第1位與失配位重新匹配,避免匹配串從頭開始!如下圖所示:

(即重新匹配剛才的失配位)

接下來最重要的,也是KMP演算法的核心所在:就是next陣列的求解! 不過,在這裡我找到了一個全新的理解方法!如果你懂的上面我寫的的,那麼下面的內容你只需稍微思考一下就行了!

跟剛才一樣,我用一句話來闡述一下next陣列的求解方法,其實也就是兩個字:

繼承

a、當前面字元的前一個字元的對稱程度為0的時候,只要將當前字元與子串第一個字元進行比較。這個很好理解啊,前面都是0,說明都不對稱了,如果多加了一個字元,要對稱的話最多是當前的和第一個對稱。比如agcta這個裡面t的是0,那麼後面的a的對稱程度只需要看它是不是等於第一個字元a了。

b、按照這個推理,我們就可以總結一個規律,不僅前面是0呀,如果前面一個字元的next值是1,那麼我們就把當前字元與子串第二個字元進行比較,因為前面的是1,說明前面的字元已經和第一個相等了,如果這個又與第二個相等了,說明對稱程度就是2了。有兩個字元對稱了。比如上面agctag,倒數第二個a的next是1,說明它和第一個a對稱了,接著我們就把最後一個g與第二個g比較,又相等,自然對稱成都就累加了,就是2了。 

c、按照上面的推理,如果一直相等,就一直累加,可以一直推啊,推到這裡應該一點難度都沒有吧,如果你覺得有難度說明我寫的太失敗了。

當然不可能會那麼順利讓我們一直對稱下去,如果遇到下一個不相等了,那麼說明不能繼承前面的對稱性了,這種情況只能說明沒有那麼多對稱了,但是不能說明一點對稱性都沒有,所以遇到這種情況就要重新來考慮,這個也是難點所在。

如果藍色的部分相同,則當前next陣列的值為上一個next的值加一,如果不相同,就是我們下面要說的!

如果不相同,用一句話來說,就是:

從前面來找子前後綴。

1、如果要存在對稱性,那麼對稱程度肯定比前面這個的對稱程度小,所以要找個更小的對稱,這個不用解釋了吧,如果大那麼就繼承前面的對稱性了。

2、要找更小的對稱,必然在對稱內部還存在子對稱,而且這個必須緊接著在子對稱之後。

如果看不懂,那麼看一下圖吧!

好了,我已經把該說的儘可能以最淺顯的話和最直接的圖展示出來了,如果還是不懂,那我真的沒有辦法了!

說了這麼多,下面是程式碼實現:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N 100
 
void cal_next( char * str, int * next, int len )
{
    int i, j;
 
    next[0] = -1;
    for( i = 1; i < len; i++ )
    {
        j = next[ i - 1 ];
        while( str[ j + 1 ] != str[ i ] && ( j >= 0 ) )
        {
            j = next[ j ];
        }
        if( str[ i ] == str[ j + 1 ] )
        {
            next[ i ] = j + 1;
        }
        else
        {
            next[ i ] = -1;
        }
    }
}
 
int KMP( char * str, int slen, char * ptr, int plen, int * next )
{
    int s_i = 0, p_i = 0;
 
    while( s_i < slen && p_i < plen )
    {
        if( str[ s_i ] == ptr[ p_i ] )
        {
            s_i++;
            p_i++;
        }
        else
        {
            if( p_i == 0 )
            {
                s_i++;
            }
            else
            {
                p_i = next[ p_i - 1 ] + 1;
            }
        }
    }
    return ( p_i == plen ) ? ( s_i - plen ) : -1;
}
 
int main()
{
    char str[ N ] = {0};
    char ptr[ N ] = {0};
    int slen, plen;
    int next[ N ];
 
    while( scanf( "%s%s", str, ptr ) )
    {
        slen = strlen( str );
        plen = strlen( ptr );
        cal_next( ptr, next, plen );
        printf( "%d\n", KMP( str, slen, ptr, plen, next ) );
    }
    return 0;
}