我也學算法 - KMP算法

一直以來,對算法都是理論大於實際,甚至沒有實際.

最近由於項目需要.從新瞭解了一下KMP算法.唉,討厭這種被動的學習過程.

不過KMP算法還是很有意思的,用了兩天的時間才總算是弄懂了.期間參考了網上的博文和數據結構.下面分享一下KMP算法的心得.

KMP的總體思想是利用模式串本身的特性來優化匹配的步驟.如何利用自身的特性呢,KMP藉助一個數組來實現,也就是大多數教程中提到的next數組.後面我會介紹next數組是如何構建和使用的.

前面提到KMP算法需要模式串滿足一定的條件,那麼這個條件是什麼呢.這裏直接引用數據結構書中的等式:

當k < j 時,  t1t2…tk-1tk = tj-(k-1)tj-k… tj-1 tj.

如果沒有這個等式,那麼KMP算法無異於浪費了next大小的空間的最普通的字符串匹配算法.


下面用一張圖來描述一下這個等式在匹配子串的時候起到的作用:

(本圖片假定數組下標從1開始)首先我們找到等式: t1t2 = t6t7.所以當t8不匹配的時候,KMP算法會自動對齊t1 t2 然後用t3和母串進行匹配.


那麼根據以上的分析,我們可以得出以下的幾個結論(可能得出的有點倉促,同學們還是需要結合課本來進行理解)

1. next數組和模式串的下標是一一對應的.

2. 每個字符對應的next保存的是其前一個字符匹配的前綴的下標.從上面的圖中可以看到next[8]保存的是3.

3. 第一個字母沒有前綴,所以它的next保存的是一個無效的下標.在實際編程中,可以是-1

4. 如果一個字符的前面沒有滿足等式的子串,則其next保存的是模式串的首字母的下標.


根據我們的結論,下面給出KMP算法的實現:

void kmp_get_next(char *pattern, int next[])
{
    int i = 0, j = 0;

    next[i] = -1;
    
    while (i < strlen(pattern) - 1)
    {
        if (j == i || pattern[i] == pattern[j])
        {
            if (j != i)
            {
                j++;
            }

            i++;
            
            next[i] = j;
        }
        else if (j != 0)
        {
            j = next[j];
        }
        else
        {
            i++;
        }
    }
}

void kmp_print_next(char *pattern, int next[])
{
    int i = 0; 

    if (NULL == pattern)
    {
        return;
    }

    printf("%s 's next array is : \n", pattern);
    for (i = 0; i < strlen(pattern); i++)
    {
        printf("%d\t", next[i]);
    }
}

int kmp_is_match(char *pattern, char *basestr, int next[])
{
    int i = 0; 
    int j = 0; 

    while (i < strlen(basestr))
    {
        if (basestr[i] == basestr[j])
        {
            if (j == strlen(pattern))
            {
                return 0;
            }

            i++;
            j++;

        }
        else
        {
            j = next[j];
            if (j == -1)
            {
                i++;
            }
        }
    }

    return -1;
}

int kmp_get_match(char *pattern, char *basestr, int next[])
{
    int i = 0; 
    int j = 0; 

    while (i < strlen(basestr))
    {
        if (basestr[i] == basestr[j])
        {
            if (j == strlen(pattern))
            {
                return i;
            }

            i++;
            j++;
        }
        else
        {
            j = next[j];
            if (j == -1)
            {
                i++;
            }
        }
    }

    return -1;
}

#include "kmp.h"

int main(int argc, char **argv)
{
    int ret = 0;
    int i = 0;
    char *pattern = "abaabc";
    int next[10] = {0};

    char *basestr = "abcabcabcabccacabdabaabcabceadb";

    kmp_get_next(pattern, next);

    printf("%s matched\n", kmp_is_match(pattern, basestr, next) == 0 ? "is" : "not");

    ret = kmp_get_match(pattern, basestr, next);

    return 0;
}

KMP算法其實理解起來並不難,比較難的就是構造next數組.至少對我而言是這樣.一旦數組構造出來,那剩下的就都好辦了.

可能是我理解的還不夠深把,寫之前覺得有很多內容可以分享,但是開始寫以後發現不知道該寫什麼.希望能夠對同學們有所啓發.




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