KMP算法

今天去面試華爲,面試官要求寫個子串匹配,求出母串中所有子串出現的首位置,我用了兩個ij兩個for循環,在j中修改了i的值,被面試官說道這種耦合的代碼不是科班生寫出來的,被糊了一臉結束了技術面試。掛在了華爲一面的羞恥牆上。

總歸是自己心態和水平不夠。以前也不愛寫博客,寫下這個,當做筆記提醒自己。

好了,來正文。

KMP算法的訣竅在於在將子串與自己匹配,算出了匹配不成功時子串(模式串)開始匹配的位置,即模式串相對於普通for循環開始的下一個位置向右滑動的長度。我知道有些繞口。關於普通for循環開始的下一個位置是哪個位置,可以參考

http://blog.csdn.net/joylnwang/article/details/6778316

博客的第二個圖,給出了每次模式串匹配的具體首位置和匹配不成功的結束位置。

你可以這麼想,在母串S模式串T比較過程中,當它一直可以比較到模式串結束,那自然是找到了模式串出現的位置。如果從模式串的0位置比較到K位置,而下一個位置K+1位置字母比較失敗,即S[某個位置X]!=T[K+1],事實上,從X-K-2位置到X-1位置的字母我們是知道的,就是模式串0K的字母,因爲它們一直匹配到K時一直是成功的。而且,這個長度K+1(從0到K嘛)肯定小於模式串的長度,因爲它匹配失敗了,沒有到末尾。

這個有什麼用呢。本來,按照for循環方法,你要回到X-K-1位置從模式串的0位置開始匹配,而現在不用了。我們有了X前的K+1個元素,完全可以從現在的X位置開始繼續匹配,唯一要考慮的是模式串要從哪個位置開始比較。這個位置肯定是X前的連續N個元素和模式串的前N個元素是一樣的。(模式串匹配就是要子串與模式串一致啊,這個應該是我強行解釋一波了)。這個N應該在0到K範圍內儘可能的大,什麼意思呢,假如模式串是ababdea,而已在母串中匹配到的是ababaX位置是最後一個a,它與模式串中的d匹配失敗,那麼與它匹配的模式串字母有可能是位於0位置的第一個a,也有可能是2位置的第二個a,當然應該是第二個。

爲什麼N<=K,因爲匹配不成功的話母串X位置不變,但子串在左移啊。

那麼問題來了,這個N的大小是多少。這個就是KMP算法中的next[]了,每個i對應位置的N即是next[i]

前面說到,匹配不成功的話,X-K-2X-1位置的元素我們是知道的,就是模式串0位置到K位置的元素。那麼X位置對應的N,不就是K+1位置對應的N嗎。那麼,我們只需要將模式串T每個元素的N值求出來,就可以在匹配時使用,而不用*求母串S*每個元素的N值。這腦洞,一般人沒這麼大,D.E.Knuth,J.H.Morris和V.R.Pratt這種天才和普通人就是不一樣。

那麼,這個求母串S每個元素的N值,即next[]的方法,就是我們一開始說的子串與自身的匹配。這麼說是模糊且不負責任的。大家想想,如果後一個元素與前一個元素相同,那麼後一個元素的N值比前一個大1,像是aaaab匹配到b失敗,那麼模式串是aaaa???…對不對,那麼b匹配的位置應該由第一個的4變成3。這裏前後相同元素的大小關係就很清晰了。這裏有個大腦洞,就是i位置的元素決定了next[i+1]的值,因爲比較是從第二個元素與第一個元素比較開始,這個腦洞大家好好琢磨琢磨。

那麼模式串要是不是連續等值怎麼辦。那麼很明顯,i位置能決定的next[i+1]是由next[i]i位置元素是否等值決定的(多想想N的定義)。如果等值,說明可以在next[i]的後一個位置元素與模式串的i位置匹配,那麼next[i+1]自然就是next[i]+1;這個是回到了N的定義,即i+1位置前的N個元素與0N-1位置的N個元素相等。

這裏留點空間多想一想。

那如果i位置元素與next[i]不等,那麼N要前移,注意,由定義來,N前一個元素則是next[next[i]]。直到next[某個次數的遞歸]==i,或是直接遞歸到模式串第一個元素next[0]纔會結束,這才能決定next[i+1]的值。

我使用的是java,所以位置都是從0開始,與清華大學嚴蔚敏先生與吳偉明先生編寫的數據結構教材有些區別。

說到這裏,KMP算法就差不多了。回到那句話,KMP是模式串與自身匹配達到每次匹配模式串左移長度的算法,即next[]。下面貼上java實現代碼,請各位斧正。其實就是教材上的代碼,但c語言並沒有相關的字符串結構,也是我對此書懷有詬病的原因。代碼清晰但當初不會實現,以至於老是覺得自己理解還行但其實並沒有掌握。今日之羞恥,雖不過一笑,還是可惜。也警戒諸位學弟學妹,萬萬不要當時覺得似是明瞭就完事大吉,學到的東西還是要練習才能掌握。也希望處於經濟不景氣的今年的我找工作順利。

public class KMP {
    static void get_next(String T,int next[]){
        int i=0;
        int j=-1;
        next[0]=-1;
        while(i<T.length()-1){
            if(j==-1||T.charAt(i)==T.charAt(j)){
                i++;
                j++;
                next[i]=j;
            }
            else j=next[j];
        }
    }
    static  int Index_KMP(String S,String T,int pos,int next[]){
        int i=pos;
        int j=0;
        while(i<S.length()&&j<T.length()){
            if(j==-1||S.charAt(i)==T.charAt(j)){
                i++;
                j++;
            }
            else j=next[j];
        }
        if(j>=T.length()) return i-T.length();
        else return -1;
    }
    public static void main(String[] args) {
    //  int[] next=new int[1000];
    //  String S="aghajghagkjhasigfwasdgh";
    //  String T="agkjha";
    //  get_next(T,next);
    //  System.out.println(Index_KMP(S,T,0,next));
    }

}

排版諸多不足,所學亦是淺薄,且當分享,還望海涵。
——2015.09.14凌晨

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