如果你看不懂KMP算法,那就看一看這篇文章( 絕對原創,絕對通俗易懂)

如果你看不懂KMP算法,那就看一看這篇文章(絕對原創,絕對通俗易懂)

 

KMP算法,俗稱“看毛片”算法,是字符串匹配中的很強大的一個算法,不過,對於初學者來說,要弄懂它確實不易。整個寒假,因爲家裏沒有網,爲了理解這個算法,那可是花了九牛二虎之力!不過,現在我基本上對這個算法理解算是比較透徹了!特寫此文與大家分享分享!

我個人總結了,KMP算法之所以難懂,很大一部分原因是很多實現的方法在一些細節的差異。怎麼說呢,舉我寒假學習的例子吧,我是看了一種方法後,似懂非懂,然後去看另外的方法,就全都亂了!體現在幾個方面:next數組,有的叫做“失配函數”,其實是一個東西;next數組中,有的是以下標爲0開始的,有的是以1開始的;KMP主算法中,當發生失配時,取的next數組的值也不一樣!就這樣,各說各的,亂的很!

所以,在闡述我的理解之前,我有必要說明一下,我是用next數組的,next數組是以下標0開始的!還有,我不會在一些基礎的概念上浪費太多,所以你在看這篇文章時必須要懂得一些基本的概念,例如樸素字符串匹配”“前綴後綴等!還有就是,這篇文章的每一個字都是我辛辛苦苦碼出來的,圖也是我自己畫的!如果要轉載,請註明出處!好了,開始吧!

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

根據KMP算法,在該失配位會調用該位的next數組的值!在這裏有必要來說一下next數組的作用!說的太繁瑣怕你聽不懂,讓我用一句話來說明:

返回失配位之前的最長公共前後綴!

好,不管你懂不懂這句話,我下面的文字和圖應該會讓你懂這句話的意思以及作用的!

首先,我們取之前已經匹配的部分(即藍色的那部分!)

我們在上面說到next數組的作用時,說到最長公共前後綴,體現到圖中就是這個樣子!

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

沒錯,這個就是next數組的作用了:

返回當前的最長公共前後綴長度,假設爲len。因爲數組是由0開始的,所以next數組讓第len位與主串匹配就是拿最長前綴之後的第1位與失配位重新匹配,避免匹配串從頭開始!如下圖所示!

(重新匹配剛纔的失配位!)

 

如果都說成這樣你都不明白,那麼你真的得重新理解什麼是KMP算法了!

 

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

 

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

繼承

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

b、按照這個推理,我們就可以總結一個規律,不僅前面是0呀,如果前面一個字符的next值是1,那麼我們就把當前字符與子串第二個字符進行比較,因爲前面的是1,說明前面的字符已經和第一個相等了,如果這個又與第二個相等了,說明對稱程度就是2了。有兩個字符對稱了。比如上面agctag,倒數第二個anext1,說明它和第一個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;
}

如果有什麼問題,歡迎評論指正!還是大一新手,很需要進步!


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