KMP算法學習筆記

咱們在一些文本編輯器中常常須要用到的一個功能就是CTRL+F(查找功能),簡單來講其實就是在一個大的字符串(以後成爲主串)裏面查找一個小的字符串(以後成爲模式串)是否是被包含在主串當中。通常比較有名的就是KMP算法還有BM算法。此次先簡單說一下KMP算法算法

KMP(Knuth Morris Pratt)算法,名字就是三個做者的名字拼起來的,沒啥特別含義,因此打出來三個字母都不對應的。。。
簡單講一下原理,正常若是咱們在作兩個字符串匹配的話,就是拿着子串不斷的再日後面推,不對應就日後推一位,以下圖

file
這個一步一步推就是傳說中的暴力匹配算法-BK(
Brute Force)算法,簡單易懂。可是其實在日後推的過程當中,是否是能夠找到一些規律,可讓咱們一次性後移多幾位呢?數組


在模式串和主串匹配的過程當中,咱們從後面往前面匹配,若是存在不能匹配的地方,那咱們在模式串中把這部分稱之爲壞後綴,而前面能夠匹配上的部分稱爲好前綴,就像下面這個圖
file
當出現壞後綴的時候,若是在好前綴裏面存在着一個好的後綴,能夠跟前綴匹配,那咱們是否是就能夠直接挪到好的後綴那裏,就好比下圖,好前綴裏面的aba這一段字符串,咱們在好前綴的後面發現了同樣的字符串,那咱們其實就能夠直接挪到後面的aba的位置,再看看後續是否匹配(可能這段會繞一點,其實在這裏咱們能夠只看模式串,後面主要也是經過模式串來進行分析)。雖然圖裏面可能只是挪多了一位,可是在實際狀況下可能就能夠提升更多的效率。
file
按照上面的思路,咱們能夠把模式串裏面的好前綴拿出來,單獨分析,其實咱們要作的就是把在好前綴裏面,看看有沒有後綴能夠跟他的前綴匹配起來,若是有的話,後綴所在的位置,在進行壞字符的匹配。
咱們把好前綴(模式串裏面)裏面的全部後綴子串中,最長匹配的那個後綴叫作最長可匹配後綴子串,相對應的前綴子串叫作最長可匹配前綴子串,以下圖
file
由於咱們用到的其實都是在模式串裏面的子串,那在匹配以前,其實咱們是否是能夠先找找好模式串的全部前綴子串是否有對應的後綴子串呢,有的話咱們就記錄下來, 作成一個數組,是否是就能夠重複使用了,就像下面這個圖同樣。
file
咱們直接看最後面一列的next值,這個就是咱們最後要作出來的數組,數組的下標就是前綴子串的長度,下標對應的值就是在這個長度的前綴子串中能夠找到最長匹配後綴的一個前綴最後面字符的下標(這段話描述起來是有點很差講),就好比下標爲2時,前綴字符是aba,第一個a和最後一個a匹配上,因此next[2]=0(第一個a的位置),在下標爲3時,前面有一個ab,後面有一個ab,對應上了,並且也是匹配上中最長的一個後綴子串,因此next[3]=1(第一個ab中b的位置)。數據結構


在有了後綴算法的狀況下,咱們就能夠試着把算法的邏輯寫出來了,代碼以下編輯器

/*
首先經過下面的getNext方法(好像都叫作失效函數)得到模式字符串的next數組 
*  從第一位開始推主串來與模式串進行匹配,若是出現不匹配的狀況,就查找次長匹配字符串 
*  而後再來判斷每個次長字符串的下一位是否是跟主串的下一位相同,相同則再次進行下一位的判斷,經過next數組來減小匹配次數
*/
public int getKMPSelfTest(char[] fullArr, int fullArrLen, char[] modelArr, int modelArrLen) {
    //獲取nexts數組
    int[] nexts = getNexts(modelArr, modelArrLen);
    int j = 0;
    for (int i = 0; i < fullArrLen; ++i) {
        //前面有匹配上的,可是這一位不相等
        while (j > 0 && fullArr[i] != modelArr[j]) {
            j = nexts[j - 1] + 1;
        }
        
        //相等就匹配下一位
        if (fullArr[i] == modelArr[j]) {
            ++j;
        }
        // 長度與模式串相等就是至關於找到了
        if (j == modelArrLen) {
            return i - modelArrLen + 1;
        }
    }
    return -1;
}

而後實現是實現nexts數組,這裏用到了動態規劃的思想,在是用next[j]的時候假設next[j-1]是能夠直接是用的常量。函數

public static int[] getNextsSelfTest(char[] modelStringArr, int m) {
    //初始化數組
    int[] next = new int[m];
    //第一位就算啦
    next[0] = -1;
    //當前位置上面的字符與前綴字符數組對應的索引,-1則沒對應上,0爲第一位
    int j = -1;
    //第一位就不用匹配了
    for (int i = 1; i < m; i++) {
        //在i以前的位數都匹配,可是i不對應的狀況
        while (j > -1 && modelStringArr[j + 1] != modelStringArr[i]) {
            j = next[j];
        }
        //當前的i與前綴對應的狀況下,下次繼續對比下一位字符是否對應
        if (modelStringArr[j + 1] == modelStringArr[i]) {
            j++;
        }
        //把當前的j值賦值給字符數組的i索引位置,若是當前不對應的話回到模式傳中上一個匹配上的索引下標,對應的話就是next[i-1]+1
        next[i] = j;
    }
    return next;
}

我以爲算法其實在平時用到仍是挺多的,也不是說在開發的時候就去本身開發一個算法,可是在學習算法過程當中的不少點我以爲在其餘地方也是能夠用得上的。好比:學習

  1. 在須要的時候多申請一個數組或者集合來存儲一些以後比較經常使用的東西。就比如next數組
  2. 發現規律,並抽象出來。在KMP中,咱們將模式串進行位移的大小的規律抽象出來

以上內容主要根據極客時間--數據結構與算法之美課程中的字符串匹配基礎一節整理,很好的一個課程,但願你們多支持,哈哈。spa

本文由博客一文多發平臺 OpenWrite 發佈!
相關文章
相關標籤/搜索