漫畫:什麼是KMP算法?

2020-12-09 CSDN

作者 | 小灰

來源 | 程式設計師小灰(ID:chengxuyuanxiaohui)

————— 第二天 —————

————————————

前情回顧

在字符串匹配算法的前兩講,我們分別介紹了暴力算法BF算法,利用哈希值進行比較的RK算法,以及儘量減少比較次數的BM算法,沒看過的小夥伴可以點擊下方連結:

1. BF算法和RK算法

2. BM算法

如果沒時間細看也沒關係,就讓我帶著大家簡單梳理一下。

首先,給定 「主串」 和 「模式串」 如下:

BF算法是如何工作的?

正如同它的全稱BruteForce一樣,BF算法使用簡單粗暴的方式,對主串和模式串進行逐個字符的比較:

第一輪,模式串和主串的第一個等長子串比較,發現第0位字符一致,第1位字符一致,第2位字符不一致:

第二輪,模式串向後挪動一位,和主串的第二個等長子串比較,發現第0位字符不一致:

第三輪,模式串繼續向後挪動一位,和主串的第三個等長子串比較,發現第0位字符不一致:

以此類推,一直到第N輪:

當模式串挪動到某個合適位置,逐個字符比較,發現每一位字符都是匹配時,比較結束:

BF算法的缺點很明顯,效率實在太低了,每一輪只能老老實實地把模式串右移一位,實際上做了很多無謂的比較。

而BM算法解決了這一問題。它藉助「壞字符規則」和「好後綴規則」,在每一輪比較時,讓模式串儘可能多移動幾位,減少無謂的比較。

利用BM算法,上面的主串和模式串匹配只需要比較三輪:

KMP算法的整體思路

KMP算法的整體思路是什麼樣子呢?讓我們來看一組例子:

KMP算法和BF算法的「開局」是一樣的,同樣是把主串和模式串的首位對齊,從左到右對逐個字符進行比較。

第一輪,模式串和主串的第一個等長子串比較,發現前5個字符都是匹配的,第6個字符不匹配,是一個「壞字符」:

這時候,如何有效利用已匹配的前綴 「GTGTG」 呢?

我們可以發現,在前綴「GTGTG」當中,後三個字符「GTG」和前三位字符「GTG」是相同的:

在下一輪的比較時,只有把這兩個相同的片段對齊,才有可能出現匹配。這兩個字符串片段,分別叫做最長可匹配後綴子串和最長可匹配前綴子串。

第二輪,我們直接把模式串向後移動兩位,讓兩個「GTG」對齊,繼續從剛才主串的壞字符A開始進行比較:

顯然,主串的字符A仍然是壞字符,這時候的匹配前綴縮短成了GTG:

按照第一輪的思路,我們來重新確定最長可匹配後綴子串和最長可匹配前綴子串:

第三輪,我們再次把模式串向後移動兩位,讓兩個「G」對齊,繼續從剛才主串的壞字符A開始進行比較:

以上就是KMP算法的整體思路:在已匹配的前綴當中尋找到最長可匹配後綴子串和最長可匹配前綴子串,在下一輪直接把兩者對齊,從而實現模式串的快速移動。

next 數組

next數組到底是個什麼鬼呢?這是一個一維整型數組,數組的下標代表了「已匹配前綴的下一個位置」,元素的值則是「最長可匹配前綴子串的下一個位置」。

或許這樣的描述有些晦澀,我們來看一下圖:

當模式串的第一個字符就和主串不匹配時,並不存在已匹配前綴子串,更不存在最長可匹配前綴子串。這種情況對應的next數組下標是0,next[0]的元素值也是0。

如果已匹配前綴是G、GT、GTGTGC,並不存在最長可匹配前綴子串,所以對應的next數組元素值(next[1],next[2],next[6])同樣是0。

GTG的最長可匹配前綴是G,對應數組中的next[3],元素值是1。

以此類推,

GTGT 對應 next[4],元素值是2。

GTGTG 對應 next[5],元素值是3。

有了next數組,我們就可以通過已匹配前綴的下一個位置(壞字符位置),快速尋找到最長可匹配前綴的下一個位置,然後把這兩個位置對齊。

比如下面的場景,我們通過壞字符下標5,可以找到next[5]=3,即最長可匹配前綴的下一個位置:

說完了next數組是什麼,接下來我們再來思考一下,如何事先生成這個next數組呢?

由於已匹配前綴數組在主串和模式串當中是相同的,所以我們僅僅依據模式串,就足以生成next數組。

最簡單的方法是從最長的前綴子串開始,把每一種可能情況都做一次比較。

假設模式串的長度是m,生成next數組所需的最大總比較次數是1+2+3+4+......+m-2 次。

顯然,這種方法的效率非常低,如何進行優化呢?

我們可以採用類似「動態規劃」的方法。首先next[0]和next[1]的值肯定是0,因為這時候不存在前綴子串;從next[2]開始,next數組的每一個元素都可以由上一個元素推導而來。

已知next[i]的值,如何推導出next[i+1]呢?讓我們來演示一下上述next數組的填充過程:

如圖所示,我們設置兩個變量i和j,其中i表示「已匹配前綴的下一個位置」,也就是待填充的數組下標,j表示「最長可匹配前綴子串的下一個位置」,也就是待填充的數組元素值。

當已匹配前綴不存在的時候,最長可匹配前綴子串當然也不存在,所以i=0,j=0,此時next[0] = 0。

接下來,我們讓已匹配前綴子串的長度加1:

此時的已匹配前綴是G,由於只有一個字符,同樣不存在最長可匹配前綴子串,所以i=1,j=0,next[1] = 0。

接下來,我們讓已匹配前綴子串的長度繼續加1:

此時的已匹配前綴是GT,我們需要開始做判斷了:由於模式串當中 pattern[j] != pattern[i-1],即G!=T,最長可匹配前綴子串仍然不存在。

所以當i=2時,j仍然是0,next[2] = 0。

接下來,我們讓已匹配前綴子串的長度繼續加1:

此時的已匹配前綴是GTG,由於模式串當中 pattern[j] = pattern[i-1],即G=G,最長可匹配前綴子串出現了,是G。

所以當i=3時,j=1,next[3] = next[2]+1 = 1。

接下來,我們讓已匹配前綴子串的長度繼續加1:

此時的已匹配前綴是GTGT,由於模式串當中 pattern[j] = pattern[i-1],即T=T,最長可匹配前綴子串又增加了一位,是GT。

所以當i=4時,j=2,next[4] = next[3]+1 = 2。

接下來,我們讓已匹配前綴子串的長度繼續加1:

此時的已匹配前綴是GTGTG,由於模式串當中 pattern[j] = pattern[i-1],即G=G,最長可匹配前綴子串又增加了一位,是GTG。

所以當i=5時,j=3,next[5] = next[4]+1 = 3。

接下來,我們讓已匹配前綴子串的長度繼續加1:

此時的已匹配前綴是GTGTGC,這時候需要注意了,模式串當中 pattern[j] != pattern[i-1],即T != C,這時候該怎麼辦呢?

這時候,我們已經無法從next[5]的值來推導出next[6],而字符C的前面又有兩段重複的子串「GTG」。那麼,我們能不能把問題轉化一下?

或許聽起來有些繞:我們可以把計算「GTGTGC」最長可匹配前綴子串的問題,轉化成計算「GTGC」最長可匹配前綴子串的問題。

這樣的問題轉化,也就相當於把變量j回溯到了next[j],也就是j=1的局面(i值不變):

回溯後,情況仍然是 pattern[j] != pattern[i-1],即T!=C。那麼我們可以把問題繼續進行轉化:

問題再次的轉化,相當於再一次把變量j回溯到了next[j],也就是j=0的局面:

回溯後,情況仍然是 pattern[j] != pattern[i-1],即G!=C。j已經不能再次回溯了,所以我們得出結論:i=6時,j=0,next[6] = 0。

以上就是next數組元素的推導過程。

1. 對模式串預處理,生成next數組

2. 進入主循環,遍歷主串

2.1. 比較主串和模式串的字符

2.2. 如果發現壞字符,查詢next數組,得到匹配前綴所對應的最長可匹配前綴子串,移動模式串到對應位置

2.3.如果當前字符匹配,繼續循環

KMP算法的具體實現

// KMP算法主體邏輯。str是主串,pattern是模式串publicstaticintkmp(String str, String pattern) {//預處理,生成next數組int[] next = getNexts(pattern);int j = 0;//主循環,遍歷主串字符for (int i = 0; i < str.length(); i++) {while (j > 0 && str.charAt(i) != pattern.charAt(j)) {//遇到壞字符時,查詢next數組並改變模式串的起點 j = next[j];}if (str.charAt(i) == pattern.charAt(j)) { j++;}if (j == pattern.length()) {//匹配成功,返回下標return i - pattern.length() + 1;}}return-1;}// 生成Next數組privatestaticint[] getNexts(String pattern) {int[] next = newint[pattern.length()];int j = 0;for (int i=2; i<pattern.length(); i++) {while (j != 0 && pattern.charAt(j) != pattern.charAt(i-1)) {//從next[i+1]的求解回溯到 next[j] j = next[j];}if (pattern.charAt(j) == pattern.charAt(i-1)) { j++;}next[i] = j;}return next;}publicstaticvoidmain(String[] args) {String str = "ATGTGAGCTGGTGTGTGCFAA";String pattern = "GTGTGCF";int index = kmp(str, pattern);System.out.println("首次出現位置:" + index);}

【End】

阿里大牛:華先勝、丁險峰直播分享!

今晚7點,阿里巴巴集團副總裁華先勝——《人工智慧:是風、是雲,還是雨?

面向開發者詳解視覺智能技術規模化落地的挑戰;面向企業詳述如何通過核心AI技術、產品化 及平臺化實現客戶價值並構建壁壘?

相關焦點

  • KMP算法文章合集
    在大字符串尋找共出現幾次子串的位置(KMP算法C寫法) 字符串匹配(模式匹配)KMP BM 字符串匹配 KMP算法 模板 2016-8-6夏令營總結(kmp,回文串,擴展kmp) kmp字符串匹配 算法1:字符串模式匹配KMP算法 原始碼 字符串模式匹配的BF算法與KMP算法 KMP算法之
  • KMP算法詳解
    kmp算法又稱「kmp」算法,是一個效率非常高的字符串匹配算法
  • 教你初步了解KMP算法
    2.2、kmp算法有了覆蓋函數,那麼實現kmp算法就是很簡單的了,我們的原則還是從左向右匹配,但是當失配發生時,我們不用把target_index向回移動,target_index前面已經匹配過的部分在pattern自身就能體現出來,只要動pattern_index就可以了。
  • 動態規劃之 KMP 算法詳解
    kmp算法再比如類似的 txt = "aaaaaaab" pat = "aaab",暴力解法還會和上面那個例子一樣蠢蠢地回退指針i,而 KMP 算法又會耍聰明:kmp算法因為 KMP 算法知道字符 b 之前的字符 a 都是匹配的,所以每次只需要比較字符 b 是否被匹配就行了。
  • 讓你搞懂到底什麼是KMP算法
    你可能聽過KMP這個名詞,先不用管KMP是什麼,看看下面的問題:給你一個字符串"jail",讓你在「abjacalisjaisnfljailnafa這個時候,KMP算法橫空出世圖片中的內容截取於維基百科說了這麼多,這個算法究竟是如何進行字符串查找的呢?
  • 輕鬆理解什麼是KMP算法
    KMP算法 內部涉及到的數學原理與知識太多,本文只會對 KMP算法 的運行過程、 部分匹配表 、next數組 進行介紹,如果理解了這三點再去閱讀其它有關 KMP算法 的文章肯定能有個清晰的認識。以下的文字描述請結合視頻動畫來閱讀~定義Knuth-Morris-Pratt 字符串查找算法,簡稱為 KMP算法,常用於在一個文本串 S 內查找一個模式串 P 的出現位置。
  • KMP算法圖文詳解
    KMP算法是解決字符串匹配的常用算法之一,也就是在主串(比如aabbccdd)中的子串(bc)定位問題。子串稱為P,如果它在一個主串稱為T中出現,就返回它的具體位置,我們先來看看普通的字符串匹配是怎麼做的最基礎的匹配思路:從左到右一個個匹配,如果這個過程中有某個字符不匹配,將子串向右移動一位,繼續從左到右一一匹配。
  • 漫畫:什麼是紅黑樹?
    什麼情況下會破壞紅黑樹的規則,什麼情況下不會破壞規則呢?我們舉兩個簡單的慄子:1.向原紅黑樹插入值為14的新節點:2.漫畫中紅黑樹調整過程的示例是一種比較複雜的情形,沒太看明白的小夥伴也不必鑽牛角尖,關鍵要懂得紅黑樹自平衡調整的主體思想。—————END—————系列文章:《漫畫:什麼是一致性哈希?》《漫畫:什麼是B+樹?》《漫畫:什麼是B-樹?》《漫畫:什麼是跳躍表?》
  • 【算法漫畫】夜過吊橋
    這樣的做法被稱之為貪心算法,貪心算法的具體操作方式是將一個大問題拆解成多個小問題,放棄掉對全局情況的考慮,從子問題中入手,對於每個小問題獲得最優解從而得到全局最優解。它在算法競賽上的運用不僅是以一種算法的形式出現,更多是以一種思想出現,讓選手發現該數學模型可以簡化為對於子問題進行求解然後拼湊出全局最優解。 在過橋問題中,大多數同學第一時間反應出來的思路都是直接讓花費時間最小的人來回送手電,但是根據題意模擬後會發現答案並不是最優的。這個時候我們按照通常處理貪心算法的思路把整個大問題拆分成小問題,我們考慮如何運送花費時長最長的兩個人過橋。
  • 漫畫:什麼是字符串匹配算法?
    ————— 第二天 —————什麼意思呢?讓我們來舉一個例子:在上圖中,字符串B是A的子串,B第一次在A中出現的位置下標是2(字符串的首位下標是0),所以返回 2。由此得到結果,模式串 bce 是主串 abbcefgh 的子串,在主串第一次出現的位置下標是 2:以上就是小灰想出的解決方案,這個算法有一個名字,叫做BF算法,是Brute Force(暴力算法)的縮寫。
  • 使用特殊算法?日漫BLOG公布最有趣的少年漫畫排名
    研究漫畫的日本BLOG「漫畫研究室」,在最近公布了一個研究結果「歷代少年漫畫裡面最有趣的漫畫是?」。由於「最有趣」實在太過主觀,沒辦法像是銷量、連載回數來用數字量化。於是他們就使用了一個公式,將發行部數的累計排名定義為A、漫畫平均單本的發行部數定義為B。算法算出了一個排名.由於排名結果也算是中肯,也許代表這個算式可以推廣開來。  ▼網站列出了許多數據,由於過程相當複雜,這邊就省略了
  • 程式設計師必須掌握的核心算法有哪些?
    由於我之前一直強調數據結構以及算法學習的重要性,所以就有一些讀者經常問我,數據結構與算法應該要學習到哪個程度呢?,說實話,這個問題我不知道要怎麼回答你,主要取決於你想學習到哪些程度,不過針對這個問題,我稍微總結一下我學過的算法知識點,以及我覺得值得學習的算法。
  • 漫畫:什麼是人工智慧?
    什麼是人工智慧?人工智慧的評判標準是什麼?要回答這個問題,就不得不先介紹另一個著名的概念:圖靈測試。搜索算法,說得難聽一些就是暴力枚舉法。IBM研發的深藍就是利用了搜索算法,在走棋的時候會枚舉出眾多的棋局變化,對每一種局面給出相應的分數,最終選擇走出一步程序認為最優的棋。但是,為了減少計算,深藍並不會盲目地枚舉出全部棋局,而是忽略掉一些明顯是「送死」的選擇。這種算法上的優化被稱為「剪枝」。需要注意的是,圖上的搜索算法僅僅是人工智慧早期眾多算法中的一種,並非全部。
  • 資源分享:一本算法書下載和幾本算法書推薦
    仔細體味一下,在工作時、在生活中,先做什麼、後做什麼、怎麼做,我們是不是有意無意在使用算法。更不用說,作為程序靈魂的算法了。在這個正飛速智能化的時代裡,算法的重要性不言而喻。 今天給大家分享的是一本訓練算法思維的書——《算法之道》,講解了許多能夠展現算法思想的算法,算法分析及其背後的邏輯,給讀者以啟示。
  • 漫畫算法:輾轉相除法是什麼鬼?
    輾轉相除法, 又名歐幾裡得算法(Euclidean algorithm),目的是求出兩個正整數的最大公約數。它是已知最古老的算法, 其可追溯至公元前300年前。這條算法基於一個定理:兩個正整數a和b(a>b),它們的最大公約數等於a除以b的餘數c和b之間的最大公約數。比如10和25,25除以10商2餘5,那麼10和25的最大公約數,等同於10和5的最大公約數。
  • 漫畫:什麼是桶排序?
    那麼,桶排序當中所謂的「桶」,又是什麼概念呢?每一個桶(bucket)代表一個區間範圍,裡面可以承載一個或多個元素。桶排序的第一步,就是創建這些桶,確定每一個桶的區間範圍:下面我們來逐步分析算法複雜度:第一步求數列最大最小值,運算量為n。第二步創建空桶,運算量為m。第三步遍歷原始數列,運算量為n。第四步在每個桶內部做排序,由於使用了O(nlogn)的排序算法,所以運算量為 n/m * log(n/m ) * m。第五步輸出排序數列,運算量為n。
  • 齊姐漫畫:排序算法(二)
    這裡還有個問題,就是什麼時候能夠解決小問題了?答:當只剩一個元素的時候,直接返回就好了,分解不了了。這就是遞歸的 base case,是要直接給出答案的。要注意,這裡的等於號跟哪邊,會影響這個排序算法的穩定性。
  • 算法是什麼:計算機領域中算法的科普
    當你使用搜尋引擎(例如Google Chrome、Mozilla Firefox等)的時候,後臺發生了什麼?當你詢問虛擬助手(例如Alexa、Google助手或Siri)的時候,後臺發生了什麼?它們怎麼會知道答案?為何它們會顯示正確答案?所有這些都要感謝算法。
  • 漫畫:什麼是神秘的「凱利公式」?
    想看更多小灰用漫畫講算法?可以戳下方小程序查看:更多精彩推薦尤雨溪:重頭來過的 Vue 3 帶來了什麼?原來 Kylin 的增量構建,大有學問! | 原力計劃可怕!
  • 算法聖經《算法導論》第三版習題答案開源!
    》——一本每個程式設計師都會接觸的算法經典教材!不論你是寫 Java、C++、Python;也無論你是做前端還是 AI 人工智慧,都或多或少地接觸算法。《算法導論》這本書,可以稱作是算法學習領域的聖經了。今天給大家收集了一份非常不錯的資源,就是《算法導論》第三版的習題答案完整版!祝你事半功倍~書籍介紹: