KMP算法筆記

一、簡述

KMP算法是對BF(Brute Force)算法做了很大改進的模式匹配算法,可以將時間複雜度從O(m*n)降低到O(m+n)。

二、KMP算法的思想

設主串s="acabaabaabcacaabc",模式t="abaabcac"

(一)分析BF算法

BF算法匹配過程如下:

第1趟

第2趟

  

第3趟

  

第4趟

第5趟

第6趟

分析BF算法的執行過程,發現造成BF算法慢的主要原因是回溯,在某一趟的匹配過程中,一旦匹配失敗,主串要回到本趟開始開始匹配字符的下一個字符,模式串要回到第一個字符,而這些回溯有時候是不必要的。對於第3趟來說,因爲s8 != t6,所以才需要進行第4趟的匹配,但是第4趟的匹配是不必要的,因爲在第3趟中,有s4 = t2,而t1 != t2,所以,s4 != t1。 同理第5趟匹配也是不必要的,因此可以從第3趟直接到第6趟。進一步分析第6趟,s6和t1的比較是多餘的,s7和t2的比較也是多餘的,因爲第3趟中有,s3~s7=t1~t5,則s6=t4,s7=t5,又因爲t1t2=t4t5,必有s6s7=t1t2,因此第6趟的比較可以從s8和t3開始,也就是說,在第3趟的比較中,s8和t6匹配失敗後,i指針不動,將模式串向右“滑動”,用t3對準s8繼續匹配,依次類推。用這樣的處理方法,指針i不需要進行回溯。

綜上所述,當si和tj匹配失敗後,我們希望指針i不回溯,而是讓模式串向右滑動至某個位置k,使得si和tk繼續比較。要滿足這個假設,則需要滿足:

t1t2...tk-1 = si-k+1si-k+2...si-1                                 

等號左邊是模式串的前k-1個字符,等號右邊是主串si之前的k-1個字符。

又因爲k<j,則:

t1t2...tk-1 = tj-k+1tj-k+2...tj-1                                                   (1.1)

結論:

在si和tj匹配失敗後,如果在模式串中存在滿足式1.1的字串,即模式串的前k-1個字符和模式串tj前的k-1個字符相等時,模式串就可以滑動到tk和si對準繼續比較。

(二)next函數

模式串的每一個tj都對應一個k值,由式1.1可知,這個k值只依賴與模式串本身,與主串無關。用函數next(j)來表示tj對應的k值。由以上分析可知,next(j)函數的性質如下:

1)next(j)是一個整數,且0<=next(j)<j;

2)  爲了使得t的右移不丟失任何匹配成功的可能,當有多個滿足式1.1的k值時,應取最大的,這樣向右滑動的距離最短,向右滑動的字符爲j-next(j)個;

3)當不存在滿足式1.1的字串時,則k=1;即模式串t1和si匹配,此時模式串滑動最遠,爲j-1個字符。

next函數定義如下:

(三)KMP算法

假設模式串t字符對應的每一個k值都計算好,並且存放在next數組中。指針i和指針j分別指向主串和模式串,依次比較,當si=tj時,指針i和指針j加1,繼續匹配;當si != tj時,指針i不變,j=next[j],繼續匹配;當j=0時,指針i和指針j加1,即模式串退回至第一個字符,主串移動到下一個字符繼續匹配。

 

三、KMP算法代碼實現

package DataStructureandAlgorithm;

/**
 * @author
 * @date 2020/1/11 21:07
 */
public class KMP {

    /**
     * @description next[j]值的含義:
     * 1、表示主串與模式串匹配失敗後,
     * 模式串應該以索引next[j]字符的值與主串匹配失敗的索引的值繼續比較
     * 2、表示索引j字符前的最大前綴後綴的後一個字符所在的索引
     * */
    public int[] getNext(String t){
        int k=-1,j=0;
        int len = t.length();
        int[] next = new int[len];
        //模式串的第一個字符next值爲-1
        next[0] = -1;
        while(j<len-1){
            if(k == -1 || t.charAt(k) == t.charAt(j)){
                k++;
                j++;
                next[j] = k;
            }else{
                k = next[k];
            }
        }
        return next;
    }

    //返回模式串在主串的索引
    public int getIndex(String s,String t,int next[]){
        int i = 0,j = 0;
        int sLen = s.length();
        int tLen = t.length();
        while(i<sLen && j<tLen){
            if(next[j] == -1 || s.charAt(i) == t.charAt(j)){
                i++;
                j++;
            }else{
                j = next[j];
            }
        }
        if(j == tLen){
            return i-tLen;
        }else{
            return -1;
        }
    }

    public static void main(String[] args){
        KMP kmp = new KMP();
        //s = acabaabaabcacaabc
        //t = abaabcac
        String s = "aabcbabcaabcaabab";
        String t = "abcaababc";
        int next[] = kmp.getNext(t);
        System.out.println(kmp.getIndex(s,t,next));

    }
}

 

 

發佈了45 篇原創文章 · 獲贊 2 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章