KMP算法的JAVA實現

KMP算法的JAVA實現

什麼是KMP算法

Knuth-Morris-Pratt算法(簡稱KMP)是經常使用的字符串匹配算法之一。html

假設如今有一個模式串a="ABACABAD"和一個主串b="BBC ABACABACABAD ABCDABDE",要判斷主串b是否包含模式串a,若是包含,則返回出模式串在主串的位置下標。算法

易知使用暴力匹配算法的時間複雜度爲O(m*n),其中m和n爲模式串和主串的長度。而使用KMP算法,則能在線性時間O(m+n)中完成匹配工做。數組

KMP算法實現邏輯

使用暴力匹配算法時,每次不匹配,都須要從主串下一個位置從頭匹配一次模式串,這種回溯工做,致使效率低下。KMP算法核心思想是充分利用上次不匹配時的計算結果,避免"一切從新開始"的計算工做。ide

如下經過一個簡單的例子進行說明:3d

一、首先,使用主串的第一位與模式串的第一位進行比較,若是不一樣,則將主串的第二位與模式串的第一位進行比較,以此類推。日誌

ce14caa5a907fe8b75632afcd9a5ddb2.jpeg比較主串第一位與模式串第一位的字符0e5e0326ce7886b1ddb6517310a5934b.jpeg比較主串第二位與模式串第一位的字符htm

二、直到主串有一個字符與模式串的第一位相同,則比較主串下一個位置的字符,是否與模式串的第二位相同,以此類推。blog

9a8d63b215b95d16206d88fd5d541072.jpeg主串中的字符匹配到模式串的第一位字符dd66e810c010e97b1ffd2bd135e25246.jpeg比較主串的下一個字符與模式串的第二位字符字符串

三、當匹配到某個位置,主串與模式串的字符不一樣時,此時不直接從主串下一個位置,再從頭逐個比較。由於在比較過程當中,咱們能夠知道兩個細節:get

(1)模式串的前面部分的字符串內容是與主串的部分字符是相同的。

(2)在該模式串"ABACABAD"中,下標0~2的字符是與下標4~6的字符是相同的。

所以,咱們直接使用下標位置爲3的字符與主串進行比較,這樣就能大大提升效率了。

2ce7f2eb3233b6f4343a943c9d9e745a.jpeg主串字符C與模式串D不匹配3ea79a6626d6caca196ad56b9ecd5c18.jpeg模式串下標0~2的字符是與下標4~6的字符相同,所以也與主串的前三個位置的字符是匹配的08426a8d72d972f387a03ebce4ba946b.jpeg不重頭開始比較,而是比較模式串下標3的字符與主串中的字符是否相同

四、以此類推,直到匹配到模式串的最後一位,或者掃描完主串。

b6c471b83368247f5bb015849be12768.jpeg匹配到模式串的最後一位

部分匹配表

在匹配步驟3中,其實利用了模式串自己字符的組合順序信息,在KMP算法中,咱們須要將該字符組合順序信息記錄起來,稱之爲"部分匹配表"。

"部分匹配表"是如何產生的呢?首先,要了解兩個概念:"前綴"和"後綴"。 "前綴"指除了最後一個字符,一個字符串的所有頭部組合,"後綴"指除了第一個字符,一個字符串的所有尾部組合。

例如字符串a="ABCAB",前綴字符串集合爲[A, AB, ABC,ABCA],後綴字符串集合爲[B, AB, CAB,BCAB],能夠看到前綴和後綴有相同的子串[AB]。

部分匹配值,其實就是計算出下標在0~i的子字符串中(i<=a.length),前綴與後綴最長相同子串的長度

"部分匹配表"計算規則可參考阮一峯老師的日誌「 字符串匹配的KMP算法」。

咱們根據這個規則,可計算模式串a="ABACABAD"的部分匹配表,以下:

9de96682556dde2b8ee9077d6dbd4c30.png

KMP算法的JAVA 代碼實現

1.計算部分匹配值。

public static int[] kmpnext(String dest){
    int[] next = new int[dest.length()];
    next[0] = 0;

    for(int i = 1,j = 0; i < dest.length(); i++){
        while(j > 0 && dest.charAt(j) != dest.charAt(i)){
            j = next[j - 1];
        }
        if(dest.charAt(i) == dest.charAt(j)){
            j++;
        }
        next[i] = j;
    }
    return next;
}

代碼說明:

1)聲明部分匹配表數組,用於存儲匹配值。

2)當字符串爲空字符串str="",沒有先後綴字符串,所以最長匹配值爲0,next[0] = 0。

3)循環字符串,計算出下標在0~i的字符串的部分匹配表,i初始化爲1。j用於記錄前綴與後綴最長相同子串的長度。

(a) 若是在0~i的子字符串,j=0,而且dest.charAt(j) != dest.charAt(i)時,表示在0~i這一段中,先後綴字符串集合中沒有相同字符串,所以next[i]=j(即next[i]=0)。

(b) 若是在0~i的子字符串,j=0,dest.charAt(j) == dest.charAt(i)時,表示在0~i這一段中,先後綴字符串集合中有一個字符串相同,所以j++;next[i]=j;(即next[i]=1)。

(c) 若是在0~i的子字符串,dest.charAt(j) == dest.charAt(i)時,若是j>0,則表示上一輪比較,在0~i-1的子字符串中,前綴與後綴有相同子串。所以在0~i這一段中,前綴與後綴也有相同子串,而且最長的共有字符串長度爲j++。所以j++;next[i]=j。

(d) 若是在0~i的子字符串,j>0,dest.charAt(j) != dest.charAt(i)時,則表示上一輪比較時,字符串[0~j-1]是字符串[0~i-1]中,先後綴的最長相同字符串,若是咱們找到在字符串[0~j-1]中的最長先後綴相同字符串(記做maxComStr),繼續比較maxComStr下一位與dest.charAt(i),則能減小比較次數。經過部分匹配表中可見,next[j-1]爲[0~j-1]中先後綴最長相同字符串的長度,咱們也能夠理解爲是最長相同字符串下一個字符的下標,所以j=next[j-1],舉例說明:

6a96c3f34c97051b8567a10801d11b15.jpegdest.charAt(j) != dest.charAt(i)a3a008f590afcb604954496c8c9e66ce.jpeg字符串[0~j-1]中,前綴字符串集合爲[A,AB],後綴字符串集合爲[A,BA],最長共有元素爲A,j=next[j-1],則j移動到了該最長前綴字符串下一位18f23eb9512000b9279e31d4f7da2630.jpeg繼續比較該最長前綴字符串下一位與dest.charAt(i)

2.比較模式串和主串。

public static int kmp(String str, String dest){
    //1.首先計算出部分匹配表
    int[] next = kmpnext(dest);
    //2.查找匹配位置
    for(int i = 0, j = 0; i < str.length(); i++){
        while(j > 0 && str.charAt(i) != dest.charAt(j)){
            j = next[j-1];
        }
        if(str.charAt(i) == dest.charAt(j)){
            j++;
        }
        if(j == dest.length()){
            return i-j+1;
        }
    }
    return -1;
}

代碼說明:

1)計算部分匹配表。

2)j爲模式串a下標,i爲主串b下標。循環主串,查找匹配位置。

(1) 若是j=0,而且str.charAt(i) != dest.charAt(j)時,則移動主串下標位置,比較主串下一位字符是否與模式串第一位字符相同。

(2) 若是str.charAt(i) == dest.charAt(j)時,則同時移動主串下標位置和模式串下標位置,依次比較下一位。

(3) 若是比較到模式串某個位置(j>0),str.charAt(i) != dest.charAt(j)時,則根據部分匹配表,移動到[0~j-1]字符串的先後綴最長相同字符串的後一位,繼續進行比較。如在該模式串dest ="ABACABAD"中,當j=7時,dest.charAt(7)與主串的字符不一樣。而dest[0~6]這部分字符串是與主串str[i-6~i-1]匹配的,dest[0~2]字符是與dest[4~6]的字符是相同的,由此能夠推斷出dest[0~2]的字符也與主串str[i-3~i-1]的字符是相同的。經過部分匹配表中可見,next[j-1]爲先後綴最長相同字符串的長度,咱們也能夠理解爲是最長相同字符串下一個字符的下標,所以j=next[j-1]。

(4) 當j == dest.length()時,代表完成模式串的比較,返回匹配起始位置(i-j+1)。

完整代碼以下:

public class Kmp {

    public static void main(String[] args){
        String a = "ABACABAD";
        String b = "BBC ABACABACABAD ABCDABDE";
        int result = kmp(b, a);

        //打印結果:和字符串得到匹配的位置        System.out.println("resultPosion:"+result);
    }

    /**     * KMP 匹配     */
    public static int kmp(String str, String dest){
        //1.首先計算出 部分匹配表        int[] next = kmpnext(dest);

        System.out.println("next ="+Arrays.toString(next));
        //2.查找匹配位置        for(int i = 0, j = 0; i < str.length(); i++){
            while(j > 0 && str.charAt(i) != dest.charAt(j)){
                j = next[j-1];
            }
            if(str.charAt(i) == dest.charAt(j)){
                j++;
            }
            if(j == dest.length()){
                return i-j+1;
            }
        }
        return -1;
    }

    /**     * 計算部分匹配表     */
    public static int[] kmpnext(String dest){
        int[] next = new int[dest.length()];
        next[0] = 0;

        for(int i = 1,j = 0; i < dest.length(); i++){
            while(j > 0 && dest.charAt(j) != dest.charAt(i)){
                j = next[j - 1];
            }
            if(dest.charAt(i) == dest.charAt(j)){
                j++;
            }
            next[i] = j;
        }
        return next;
    }}
相關文章
相關標籤/搜索