hihoCoder#1015 KMP算法

原題地址
之前也學習瞭解過,可是一直糊里糊塗的,沒能真正瞭解其含義。
這次剛好在hiho上看到,就隨着小hi,小ho再一次瞭解了下。
算法的求解有兩個難點(前人真的太強大了)
1、KMP算法next數組的使用
2、next數組的求解

這在小hi和小ho的hints裏都有很不錯的講解,我就直接貼過來了
小Hi和小Ho回到了學校,爲了完成河蟹託付的偉大使命,小Hi立馬把小Ho抓到了機房開始上課。

“小Ho,你來看看這樣一段原串和模式串~”小Hi說着遞上了一張紙條。

原串: bababababababababb
模式串: bababb
“嗯,這個例子中模式串bababb在原串中第13個字符開始的地方出現了”小Ho看了看,回答道。

“我們假設仍然使用最普通的方法來進行判斷,即我們先枚舉原串中的一個起始位置,然後判斷從這個位置開始的字符串是否能和模式串進行完匹配。”小HI說道,“然後我們來看這個過程中有沒有什麼可以縮減的計算量。”

“好的!”小Ho點點頭。

“你看,在起始點爲1的時候,匹配到第6個字符的時候發生了失敗,這個時候我們應當做的是是不是將模式串右移一位,然後從頭開始判斷,就像這樣?”小Hi又在紙上畫了畫,遞給了小Ho。“

原串: bababababababababb
模式串: bababb
原串: bababababababababb
模式串: bababb
”是的,然後我們發現第一位就發現不能進行匹配。“小Ho老老實實的回答。

”然後我們再將模式串右移一位,然後再從頭開始判斷,這次我們成功的越過了原串的第7個字符,在第8個字符產生了不同。“小Hi繼續往下推演。

原串: bababababababababb
模式串: bababb
”然後之後的劇情非常的相似,都是要麼最後一個字符匹配不成功,要麼就是第一個字符就匹配不成功,一直到了最後一次機會的時候才匹配成功。“小Ho做了總結。

”那你覺得這個過程中有沒有什麼沒有必要計算的呢?“小Hi於是問道。

”我是這麼認爲的,你看這條線。“小Ho在兩個串上對着的一個位置畫了一條線。

原串: babab | ababababababb
模式串: babab | b
”嗯?”

“這是我們第一次產生了字符不匹配的情況,那麼接下來的過程中一定會出現兩種情況之一:一種情況是模式串與原串的對齊點(即枚舉的原串中的起點位置)越過了這條線,仍然沒能匹配成功,而另一種情況是原串中這個位置的字符與模式串中某個位置的字符匹配上了。”小Ho分析道:”我們先不考慮第一種情況,而來看看第二種情況會發生什麼。“

原串: babab | ababababababb
模式串(對齊點=1): babab | b
模式串(對齊點=3): bab | a
”看不出嘛,小Ho你今天變成聰明瞭嘛!~”小Hi由衷的讚歎道。

“那當然,畢竟我最近在討論區解答了很多問題,這很鍛鍊人的好麼!“小Ho笑嘻嘻的回答道。

”那我也得表現下,接下來換我來說吧,反正你肯定也就差不多想到這麼多是吧!“小Hi也是看破了小Ho的底細,這般說道。於是小Ho點了點頭,讓小Hi接着說。

”我相信一個很容易注意到的事實就在於,如果我用i表示原串和模式串產生分歧的位置(模式串上的位置,注意!這個和對齊點是不一樣的東西,一個在原串上,一個在模式串上),用j表示爲了匹配掉位置i上產生分歧的字符而將模式串的對齊點移動到的位置,我們會發現,模式串[1, i-j]的這一段和[j, i - 1]這一段是相同的。比如在這個例子中i=6,j=3,我們會發現模式串[1, 3]和[3,5]是相同的。“小Hi整理了下思路,如是說道。

原串: ba | bab | a babababababb
模式串(i=1): ba | bab | b
模式串(i=3): | bab | a
”而我們同時也會發現,只有在存在一個長度k,使得模式串[1, i-k]和[k, i-1]這兩段相同的情況下,將模式串對其到位置k,才能保證原串和模式串的匹配過程能夠進入到原串的位置i是否和模式串的對應字符相同的判定,在別的情況下,根本都進入不到位置i的判斷就會發生不一致的情況了。”說着小Hi又拋出了另外一個命題。

“我已經開始有點暈了!”小Ho提出了抗議。

“那你就好好的讀一遍我剛纔說的話!然後自己在草稿紙上演算一下這個樣例,很快就可以得出結果的!”小Hi如是說道。”總而言之我們現在需要的一個數據是,這個長度k最長是多少,而且我們對於模式串的每一個位置i,都要計算這個值。”而這就是KMP中最爲重要的一個點——NEXT數組。
“那麼,爲了能夠充分理解NEXT數組,我們再來回顧一下如何使用NEXT數組~”小Hi擺出一副老師的樣子,說道。”首先我們來給出NEXT數組的數學定義~“

NEXT[0] = -1
NEXT[i] = max{ 0<=k< i | str.substring(1, k) == str.substring(i - k +1 , i) } 其中str.substring(i, j)表示str從位置i到位置j的子串,如果i>j則,substring爲空
”那麼我們對之前例子中的模式串進行求解,可以得到這樣的NEXT數組。“小Hi在紙上寫了又寫,畫了又畫。

模式串: b a b a b b
NEXT: 0 0 1 2 3 1
”然後再來看這個NEXT數組是如何使用的!爲了表明NEXT的所有使用情況,我們換一個原串。然後首先,我們第一次匹配,如果用ori表示原串,用par表示模式串,用p表示原串的下標(從1開始),用q表示模式串的下標(從1開始)的話,會發現最多匹配到p=5, q=5就不能往下匹配了,因爲此時ori[p +1]不等於par[q + 1]“小Hi爲了使說明更加簡潔,先下了一堆定義。

”好的!小Hi老師好棒!“小Ho在一旁煽風點火道。

原串(p=5): babab | abcbababababb
模式串(q=5): babab | b
”此時,令q = NEXT[q],並將ori[1..p]和par[1..q]對齊,便會發現ori[1..p]和par[1..q]仍然是一一對應的。“

原串(p=5): babab | abcbababababb
模式串(q=3): bab | abb
“此時,ori[p+1]和par[q+1]相同了,於是可以繼續往下匹配,但是到了p=7,q=5的時候又發現不能夠接着匹配了。”

原串(p=7): bababab | cbababababb
模式串(q=5): babab | b
”此時,令q = NEXT[q],並將ori[1..p]和par[1..q]對齊,便會發現ori[1..p]和par[1..q]仍然是一一對應的,這和之前是一樣的。”

原串(p=7): bababab | cbababababb
模式串(q=3): bab | abb
“此時,ori[p+1]和par[q+1]仍然不相同,於是還得令q=NEXT[q]。”

原串(p=7): bababab | cbababababb
模式串(q=1): b | ababb
“此時,ori[p+1]和par[q+1]仍然不相同,令q=NEXT[q]。”

原串(p=7): bababab | cbababababb
模式串(q=0): | bababb
“此時,ori[p+1]和par[q+1]仍然不相同,令q=NEXT[q]。”

原串(p=7): bababab | cbababababb
模式串(q=-1): | bababb
”到了這一步,就相當於我們之前所說的模式串與原串的對齊點(即枚舉的原串中的起點位置)越過了這條線(當時指C右側的那條線)的情況,這種情況下,就應當p和q均+1,然後繼續之前的操作。”小Hi擦了一把汗,說道。

“這樣一說,我就大致能夠理解NEXT數組是怎麼用來求解模式匹配問題的了,但是它是如何求的呢?一般的方法不是要O(模式串長度的立方)的麼?”小Ho問道。

“這就是我接下來要和你說的啦!”小Hi笑道:“但是讓我先喝口水!”
“首先我們不想如何求整個NEXT數組,而是假設我們已經知道了之前例子中模式串的NEXT[1..4],來求NEXT[5]如何?”小Hi建議道。

“好的!這樣我們就只需要平方級的算法就可以算出它的值了!”小Ho高興道。

“有點追求好不好!”小Hi深深的吸了一口氣:“你這樣和之前的解法有什麼不同麼!”

“似乎沒有。。那你說怎麼算吧!我反正腦子已經成漿糊了。”小Ho鬱悶道。

“我們把par.substring(1, 5)當做新的原串ori_new,然後把par.substring(1, 4)當做新的模式串par,會如何?”小Hi微微一笑。

“會。。我來試試!”小Ho接過小Hi手中的紙筆,便開始演算:“首先就直接匹配到了p=4, q=4的情況,這時候嚴格來說已經算匹配完成了,但是肯定不是就這麼結束的,此時par_new[q +1]因爲是空字符,所以肯定和ori_new[p+1]匹配不上。於是令q = NEXT[q]”

原串(p=4): baba | b
模式串(q=4): baba |
原串(p=4): baba | b
模式串(q=2): ba | b
”然後這時候ori_new[p + 1]就直接和par_new[q + 1]匹配上了,於是新的p=5,q=3,莫非……這個最後的q就是NEXT[5]!“小Ho忽然靈光一閃。

”沒錯,就是這樣!那你想想現在如何求NEXT[6]。“小Hi繼續引導小Ho。

”首先我們沒有必要重新從頭開始匹配,直接在原串和模式串的後面加上第6個字符就可以了。“小Ho分析道。

原串(p=5): babab | b
模式串(q=3): bab | abb
”沒法繼續匹配,於是令q=NEXT[q]。“

原串(p=5): babab | b
模式串(q=1): b | ababb
”還是沒法繼續匹配,於是令q=NEXT[q]。“

原串(p=5): babab | b
模式串(q=0): | bababb
”此時可以匹配了,新的p=6,q=1,所以NEXT[6]就是1!“小Ho高興道:”沒想到NEXT數組的本身會用一種遞歸的方式進行求解,真是太巧妙了!“

”那你要不要趕緊去寫一下代碼,KMP算法的代碼可是可以寫的很短很巧妙的哦!~“小Hi建議道。

”好!“

上面的有趣的對話很好的解釋了next數組和KMP算法。
下面貼上經過優化的next數組求解方法

GetNext(const char* pattern,int* next) {
    int j = -1,i = 0;
    next[0] = -1;
    while(pattern[i]) {
        if (j == -1 || pattern[j] == pattern[i]) {
            i++;
            j++;
            if (pattern[j] != pattern[i])
                next[i] = j;
            else
                next[i] = next[j];
        } else {
            j = next[j];
        }
    }
}

其中pattern即爲我們需要找到的字符串.
另外題中我們需要注意的是 我們要求的是包含了多少個這樣的子串,而不是簡單的判斷
AC的代碼:

#include<iostream>
#include<cstring>
#include<cstdio>

using namespace std;
#define _length 10001
class Solve {
 public:
    void GetNext(const char* patten,int* next);
    int KMP(const char* patten, const char* source, int* next);
};

void Solve :: GetNext(const char* pattern,int* next) {
    int j = -1,i = 0;
    next[0] = -1;
    while(pattern[i]) {
        if (j == -1 || pattern[j] == pattern[i]) {
            i++;
            j++;
            if (pattern[j] != pattern[i])
                next[i] = j;
            else
                next[i] = next[j];
        } else {
            j = next[j];
        }
    }
}

int Solve::KMP(const char* pattern, const char* source, int* next) {
    int num = 0;
    int temp = 0;
    for(int i = 0; source[i]; ++i) {
        if (source[i] == pattern[temp]) {
            temp++;
            if(temp == strlen(pattern)) {
                num++;
                temp = next[temp];
            }
        } else {
            if(temp == 0) continue;
            temp = next[temp];
            i--;
        }
    }
    return num;
}

int main (int args, char* argv[])
{
    //freopen("F:/in.txt","r",stdin);
    int count;
    cin >> count;
    string mode, source;
    Solve kmp;
    int next[_length];
    for(int i = 0; i< count; ++i) {
        cin >> mode >> source;
        kmp.GetNext(mode.c_str(), next);
        cout << kmp.KMP(mode.c_str(), source.c_str(), next)<<endl;
    }
    //fclose(stdin);
    return 0;
}

http://blog.csdn.net/v_july_v/article/details/7041827
http://www.cnblogs.com/10jschen/archive/2012/08/21/2648451.html

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