KMP算法圖文詳解

2021-03-02 Java攻城之路

KMP算法是解決字符串匹配的常用算法之一,也就是在主串(比如aabbccdd)中的子串(bc)定位問題。子串稱為P,如果它在一個主串稱為T中出現,就返回它的具體位置,我們先來看看普通的字符串匹配是怎麼做的

最基礎的匹配

思路:從左到右一個個匹配,如果這個過程中有某個字符不匹配,將子串向右移動一位,繼續從左到右一一匹配。

當匹配到如圖第四個字符位置後,匹配失敗,子串後移,繼續匹配


第一位匹配失敗,繼續後移…


直到匹配成功

代碼如下:

public class Normal {

    public static void main(String[] args) {
        int index = bf("ABCABCEFG", "ABCE");
        System.out.println(index);
    }

    public static int bf(String ts, String ps) {
        char[] t = ts.toCharArray();
        char[] p = ps.toCharArray();
        int i = 0; 
        int j = 0; 

        while (i < t.length && j < p.length) {
            if (t[i] == p[j]) { 
                i++;
                j++;
            } else {
                i = i - j + 1; 
                j = 0; 
            }
        }

        if (j == p.length) {
            return i - j;
        } else {
            return -1;
        }
    }
}

這種方式是效率最低,匹配次數最多的情況,接下來看KMP的解決思路

KMP中的PMT

KMP在遇到下圖位置時,不會很無腦的把子串的j移動到第0位,主串的i移動到第1位,然後進行T[i]==P[j]的比較


因為從圖上可以看得出後移一位後子串前三位(ABC)和主串的T[1-4](BCA)肯定是不匹配的,無需白白浪費這幾次比較,最好應該是直接讓i不變,j==0,如下圖
從這裡開始匹配,省去了前面的幾次無用匹配。

KMP思想:利用前面匹配的信息,保持i指針不變,通過修改j指針,讓子串儘量地移動到有效的位置。

整個KMP的重點就在於當某一個字符與主串不匹配時,我們應該知道j指針要移動到哪?

先用肉眼來看一下規律:


如圖:C和D不匹配了,我們要把j移動到哪?顯然是第1位。為什麼?因為前面有一個A相同可以用:
再看一種:
可以把j指針移動到第2位,因為前面有兩個字母是一樣的:
我們可以看出來,匹配失敗的時候,j變為k,j前面的的n個字符等於子串開頭到k位置的n個字符的值
即:P[0 ~ k-1] == P[j-k ~ j-1]

這時我們發現規律了,其實就是要求當前j之前的字符串也就是ABCAB它的首尾對稱的長度最大長度也就是PMT值。

PMT中的值是字符串的前綴集合與後綴集合的交集中最長元素的長度。

例如,對於」aba」,它的前綴集合為,後綴集合為。
兩個集合的交集為,
那麼長度最長的元素就是字符串」a」了,長度為1,所以對於」aba」而言,它在PMT表中對應的值就是1。
再比如,對於字符串」ababa」,它的前綴集合為,
它的後綴集合為, 
兩個集合的交集為,其中最長的元素為」aba」,長度為3。

所以上面最後一個圖的情況下,j位置之前的字符串的PMT值為2,所以j的值變成2。

KMP之next數組

那麼好了接下來核心就是求得P串每個下標元素對應的k值即可,因為在P的每一個位置都可能發生不匹配,我們要計算每一個位置j對應的k,所以用一個數組next來保存,next[j] = k,表示當T[i] != P[j]時,j應該變為k。

求next數組代碼如下

public class Next {

    public static int[] getNext(String ps) {
        char[] p = ps.toCharArray();
        int[] next = new int[p.length];
        next[0] = -1;
        int j = 0;
        int k = -1;
        while (j < p.length - 1) {
            if (k == -1 || p[j] == p[k]) {
                next[++j] = ++k;
            } else {
                k = next[k];
            }
        }
        return next;
    }
}

通過上面代碼可以直接算出j為0和1時的k,當j為0時,已經無法後退了所以設置為-1初始化值,當j為1時,它的前面只有下標0了,所以next[0]=-1,next[1]=0.

接下來就是兩種主要情況了

if (k == -1 || p[j] == p[k]) {   第一種p[j] == p[k]
    next[++j] = ++k;
} else {                         第二種p[j] != p[k]
    k = next[k];
}

第一種p[j] == p[k]

p[j] == p[k]時,有next[++j] = ++k;
因為當在p[j-1]處匹配失敗後,j-1變為k-1,從k-1處重新開始匹配,原因就是他們共同有一個前綴A,所以當p[j] == p[k]後,他們就擁有了前綴AB所以k++;

第二種p[j] != p[k]
此時代碼是:k = next[k];原因看下圖
像上邊的例子,我們已經不可能找到[ A,B,A,B ]這個最長的後綴串了,但我們還是可能找到[ A,B ]、[ B ]這樣的前綴串的。所以這個過程就像在定位[ A,B,A,C ]這個串,當C和主串不一樣了(也就是k位置不一樣了),那當然是把指針移動到next[k]。

有了next數組之後就一切好辦了,我們可以動手寫KMP算法了:

public class Kmp {
    public static int KMP(String ts, String ps) {
        char[] t = ts.toCharArray();
        char[] p = ps.toCharArray();
        int i = 0; 
        int j = 0; 
        int[] next = getNext(ps);

        while (i < t.length && j < p.length) {
            if (j == -1 || t[i] == p[j]) { 
                i++;
                j++;
            } else {
                
                
                j = next[j]; 
            }
        }

        if (j == p.length) {
            return i - j;
        } else {
            return -1;
        }
    }
}

KMP改進

KMP算法是存在缺陷的,來看一個例子:比如主串是aaaabcde,子串是aaaaax,next值為012345,當i=5時,如下圖:


我們發現,當中的②③④⑤步驟,其實是多餘的判斷。由於子串的第二、三、四、五位置的字符都與首位的「a」相等,那麼可以用首位next[1]的值去取代與它相等的字符後續next[j]的值,這是個很好的辦法。因此我們對求next函數進行了改良。

public class Next2 {
    public static int[] getNext(String ps) {
        char[] p = ps.toCharArray();
        int[] next = new int[p.length];
        next[0] = -1;
        int j = 0;
        int k = -1;
        while (j < p.length - 1) {
            if (k == -1 || p[j] == p[k]) {
                if (p[++j] == p[++k]) { 
                    next[j] = next[k];
                } else {
                    next[j] = k;
                }
            } else {
                k = next[k];
            }
        }
        return next;
    }
}

相關焦點

  • KMP算法文章合集
    –KMP算法理解(python) KMP的next數組求法詳解 KMP算法詳解 KMP算法(代碼+圖解證明) 算法之字符串匹配的KMP算法1 KMP算法解決字符串匹配問題 字符串的模式匹配(KMP)算法 字符串匹配KMP算法的理解(詳細) python實現kmp算法(學不會你噴我)
  • KMP算法詳解
    kmp算法又稱「kmp」算法,是一個效率非常高的字符串匹配算法
  • 動態規劃之 KMP 算法詳解
    KMP 算法(Knuth-Morris-Pratt 算法)是一個著名的字符串匹配算法,效率很高,但是確實有點複雜。很多讀者抱怨 KMP 算法無法理解,這很正常,想到大學教材上關於 KMP 算法的講解,也不知道有多少未來的 Knuth、Morris、Pratt 被提前勸退了。
  • 教你初步了解KMP算法
    2.2、kmp算法有了覆蓋函數,那麼實現kmp算法就是很簡單的了,我們的原則還是從左向右匹配,但是當失配發生時,我們不用把target_index向回移動,target_index前面已經匹配過的部分在pattern自身就能體現出來,只要動pattern_index就可以了。
  • 漫畫:什麼是KMP算法?
    ,利用哈希值進行比較的RK算法,以及儘量減少比較次數的BM算法,沒看過的小夥伴可以點擊下方連結:1.BF算法和RK算法2. BM算法如果沒時間細看也沒關係,就讓我帶著大家簡單梳理一下。首先,給定 「主串」 和 「模式串」 如下:BF算法是如何工作的?
  • 讓你搞懂到底什麼是KMP算法
    這個時候,KMP算法橫空出世圖片中的內容截取於維基百科說了這麼多,這個算法究竟是如何進行字符串查找的呢?首先,該算法需要在開始匹配之前收集足夠的「信息」,用於在匹配過程中給出「指導意見」這個「信息」指,根據待查找字符串T建立的一張表
  • 輕鬆理解什麼是KMP算法
    KMP算法 內部涉及到的數學原理與知識太多,本文只會對 KMP算法 的運行過程、 部分匹配表 、next數組 進行介紹,如果理解了這三點再去閱讀其它有關 KMP算法 的文章肯定能有個清晰的認識。以下的文字描述請結合視頻動畫來閱讀~定義Knuth-Morris-Pratt 字符串查找算法,簡稱為 KMP算法,常用於在一個文本串 S 內查找一個模式串 P 的出現位置。
  • 《大富翁10》新手教程圖文詳解 全關卡玩法技巧詳解
    18183首頁 大富翁10 《大富翁10》新手教程圖文詳解 全關卡玩法技巧詳解 《大富翁10》新手教程圖文詳解 全關卡玩法技巧詳解
  • fatego克洛伊馮愛因茲貝倫人物圖文詳解
    導 讀 fatego遊戲中克洛伊·馮·愛因茲貝倫相信很多玩家都有見過吧,這裡小編給大家帶來了了fatego克洛伊馮愛因茲貝倫人物圖文詳解
  • EM算法詳解
    EM算法簡介3. 預備知識    3.1 極大似然估計    3.2 Jensen不等式4. EM算法詳解    4.1 問題描述    4.2 EM算法推導流程    4.3 EM算法流程5.EM算法若干思考    5.1 對EM算法的初始化研究    5.2 EM算法是否一定收斂?    5.3 如果EM算法收斂,能否保證收斂到全局最大值?6. EM算法實例7. 對EM算法總結1.
  • 《暴雨》結局有哪些 全部結局圖文詳解
    小編今天帶來了暴雨全部結局圖文詳解,還不知道的玩家們不妨進來看看吧! 暴雨全部結局圖文詳解 Ethan Mars,喪子離婚悲劇男豬腳(7個ending) A New L... 暴雨全部結局怎麼達成?小編今天帶來了暴雨全部結局圖文詳解,還不知道的玩家們不妨進來看看吧!
  • 光碟機重裝系統教程圖文詳解版
    光碟機重裝系統教程圖文詳解版!最近有位朋友的電腦usb接口壞了,無法使用u盤啟動盤重裝系統,想知道如何使用光碟機來重裝系統。其實,使用光碟機重裝系統是一個比較傳統的重裝方法,現在確實比較少人使用。但是如果遇到以上的情況,那麼學會光碟機重裝系統就很有必要了。
  • 劍聖傳奇肉山玩法圖文詳解[圖]
    劍聖傳奇肉山玩法圖文詳解大家如果玩過DOTA的話一定會知道肉山是什麼意思,如果不知道的話就來看看小編準備的劍聖傳奇肉山玩法圖文詳解。你一定會被肉山的獨特吸引力給迷住。肉山的魅力讓你連續被虐還繼續挑戰的愛情。
  • 300頁測量員圖文詳解,學會工資暴漲!
    為了減少類似事件的發生,我特意問朋友要了這份測量員圖文解析,聽說他月工資從3K到10K就是靠的這份資料。這套測量員圖文詳解包括了工程系測量基礎、建築工程製圖與識讀、水準測量、地形測量、施工測量和測量常用的數據等內容,識圖、測量技能和常用數據等內容全都有,形式直觀,理念也比較新穎,實用性超強。
  • 常用算法詳解——列印楊輝三角形
    在中國南宋數學家楊輝1261年所著的《詳解九章算法》一書中出現。在歐洲,這個表叫做帕斯卡三角形。帕斯卡(1623----1662)是在1654年發現這一規律的,比楊輝要遲393年,比賈憲遲600年。楊輝三角是中國古代數學的傑出研究成果之一,它把二項式係數圖形化,把組合數內在的一些代數性質直觀地從圖形中體現出來,是一種離散型的數與形的結合。
  • 螺栓連接畫法圖文詳解
    原標題:螺栓連接畫法圖文詳解  螺栓連接畫法是什麼?螺栓連接的緊固件較多,有螺母和墊圈等,因此掌握螺栓連接畫法十分重要。高強螺栓試驗夾具 下面,世界工廠泵閥網為大家詳解介紹螺栓連接畫法,以供繪圖時參考。
  • 我愛拼模型稻荷神社攻略 稻荷神社圖文詳解
    18183首頁 我愛拼模型 我愛拼模型稻荷神社攻略 稻荷神社圖文詳解 我愛拼模型稻荷神社攻略 稻荷神社圖文詳解 來源
  • CAD軟體繪圖顯示線寬的圖文流程詳解
    本期模型云為您整理了CAD軟體繪圖顯示線寬的圖文流程詳解,一起來看看吧!CAD軟體繪圖怎麼顯示線寬?步驟一、打開CAD軟體並繪圖區域畫出一條直線,我們來演示CAD軟體繪圖顯示線寬的詳細流程。更多>>CAD製圖的延伸工具操作方法詳解以上就是模型云為您帶來的CAD軟體繪圖顯示線寬的圖文流程詳解了,希望通過本期的分享之後,各位小夥伴們已經學會了CAD軟體繪圖怎麼顯示線寬了哦!
  • 【SDCC 2015現場】算法實踐論壇(上):網易、京東、騰訊的算法優化...
    圖:算法實踐論壇現場SDCC大會第三天上午9:30,算法實踐論壇由宜信大數據創新中心數據科學家 項亮擔任主持,在對所有演講嘉賓進行介紹後,本次論壇正式開始現場詳解如何通過基於搜索用戶日誌挖掘、基於Query短語權重的相似性糾錯等Query優化手段實現RPM上升,「精確」召回更多廣告,提升單次點擊價格。
  • 網上訂票步驟圖文詳解
    閩南小編圖文詳解為您解答。2014年12306推出了新版網絡購票,需要了解新版12306訂票步驟可點擊(如何使用新版12306進行訂票? 訂票步驟圖文詳解)步驟一:  登陸12306跌倒不網上訂票官網,網址為:http://www.12306.cn  打開時首頁入下圖所示: