KMP算法

總結:

  • 生成和原長度相同的數組Nextarr, 每個元素的值爲原來數組此下標前最長重複子串的長度。
  • 利用Nextarr快速跳過包含相同重複子串部分

  1. str 1=‘abcdabce’
  2. Nextarr = [-1,0,0,0,0,1,2,3,0]#目的是記錄最大重複子串,後面匹配直接跳過
  3. str2 ='abce'

一個經典的例子,是判斷一個樹是否是另外一棵樹的子樹,或者是子結構(注意區分子樹和子結構)

  1. 先序列化兩顆數
  2. kmp判斷是否爲子串

 

以下轉載:

3. KMP算法

3.1 定義

    Knuth-Morris-Pratt 字符串查找算法,簡稱爲 “KMP算法”,常用於在一個文本串S內查找一個模式串P 的出現位置,這個算法由Donald Knuth、Vaughan Pratt、James H. Morris三人於1977年聯合發表,故取這3人的姓氏命名此算法。

    下面先直接給出KMP的算法流程(如果感到一點點不適,沒關係,堅持下,稍後會有具體步驟及解釋,越往後看越會柳暗花明☺):

  • 假設現在文本串S匹配到 i 位置,模式串P匹配到 j 位置
    • 如果j = -1,或者當前字符匹配成功(即S[i] == P[j]),都令i++,j++,繼續匹配下一個字符;
    • 如果j != -1,且當前字符匹配失敗(即S[i] != P[j]),則令 i 不變,j = next[j]。此舉意味着失配時,模式串P相對於文本串S向右移動了j - next [j] 位。
      • 換言之,當匹配失敗時,模式串向右移動的位數爲:失配字符所在位置 - 失配字符對應的next 值(next 數組的求解會在下文的3.3.3節中詳細闡述),即移動的實際位數爲:j - next[j],且此值大於等於1。

    很快,你也會意識到next 數組各值的含義:代表當前字符之前的字符串中,有多大長度的相同前綴後綴。例如如果next [j] = k,代表j 之前的字符串中有最大長度爲k 的相同前綴後綴。

    此也意味着在某個字符失配時,該字符對應的next 值會告訴你下一步匹配中,模式串應該跳到哪個位置(跳到next [j] 的位置)。如果next [j] 等於0或-1,則跳到模式串的開頭字符,若next [j] = k 且 k > 0,代表下次匹配跳到j 之前的某個字符,而不是跳到開頭,且具體跳過了k 個字符。

    轉換成代碼表示,則是:

  1. int KmpSearch(char* s, char* p)  
  2. {  
  3.     int i = 0;  
  4.     int j = 0;  
  5.     int sLen = strlen(s);  
  6.     int pLen = strlen(p);  
  7.     while (i < sLen && j < pLen)  
  8.     {  
  9.         //①如果j = -1,或者當前字符匹配成功(即S[i] == P[j]),都令i++,j++      
  10.         if (j == -1 || s[i] == p[j])  
  11.         {  
  12.             i++;  
  13.             j++;  
  14.         }  
  15.         else  
  16.         {  
  17.             //②如果j != -1,且當前字符匹配失敗(即S[i] != P[j]),則令 i 不變,j = next[j]      
  18.             //next[j]即爲j所對應的next值        
  19.             j = next[j];  
  20.         }  
  21.     }  
  22.     if (j == pLen)  
  23.         return i - j;  
  24.     else  
  25.         return -1;  
  26. }  

    繼續拿之前的例子來說,當S[10]跟P[6]匹配失敗時,KMP不是跟暴力匹配那樣簡單的把模式串右移一位,而是執行第②條指令:“如果j != -1,且當前字符匹配失敗(即S[i] != P[j]),則令 i 不變,j = next[j]”,即j 從6變到2(後面我們將求得P[6],即字符D對應的next 值爲2),所以相當於模式串向右移動的位數爲j - next[j](j - next[j] = 6-2 = 4)。

    向右移動4位後,S[10]跟P[2]繼續匹配。爲什麼要向右移動4位呢,因爲移動4位後,模式串中又有個“AB”可以繼續跟S[8]S[9]對應着,從而不用讓i 回溯。相當於在除去字符D的模式串子串中尋找相同的前綴和後綴,然後根據前綴後綴求出next 數組,最後基於next 數組進行匹配(不關心next 數組是怎麼求來的,只想看匹配過程是咋樣的,可直接跳到下文3.3.4節)。

3.2 步驟

  • ①尋找前綴後綴最長公共元素長度
    • 對於P = p0 p1 ...pj-1 pj,尋找模式串P中長度最大且相等的前綴和後綴。如果存在p0 p1 ...pk-1 pk = pj- k pj-k+1...pj-1 pj,那麼在包含pj的模式串中有最大長度爲k+1的相同前綴後綴。舉個例子,如果給定的模式串爲“abab”,那麼它的各個子串的前綴後綴的公共元素的最大長度如下表格所示:

比如對於字符串aba來說,它有長度爲1的相同前綴後綴a;而對於字符串abab來說,它有長度爲2的相同前綴後綴ab(相同前綴後綴的長度爲k + 1,k + 1 = 2)。

  • ②求next數組
    • next 數組考慮的是除當前字符外的最長相同前綴後綴,所以通過第①步驟求得各個前綴後綴的公共元素的最大長度後,只要稍作變形即可:將第①步驟中求得的值整體右移一位,然後初值賦爲-1,如下表格所示:

比如對於aba來說,第3個字符a之前的字符串ab中有長度爲0的相同前綴後綴,所以第3個字符a對應的next值爲0;而對於abab來說,第4個字符b之前的字符串aba中有長度爲1的相同前綴後綴a,所以第4個字符b對應的next值爲1(相同前綴後綴的長度爲k,k = 1)。

  • ③根據next數組進行匹配
    • 匹配失配,j = next [j],模式串向右移動的位數爲:j - next[j]。換言之,當模式串的後綴pj-k pj-k+1, ..., pj-1 跟文本串si-k si-k+1, ..., si-1匹配成功,但pj 跟si匹配失敗時,因爲next[j] = k,相當於在不包含pj的模式串中有最大長度爲k 的相同前綴後綴,即p0 p1 ...pk-1 = pj-k pj-k+1...pj-1,故令j = next[j],從而讓模式串右移j - next[j] 位,使得模式串的前綴p0 p1, ..., pk-1對應着文本串 si-k si-k+1, ..., si-1,而後讓pk 跟si 繼續匹配。如下圖所示:

 

    綜上,KMP的next 數組相當於告訴我們:當模式串中的某個字符跟文本串中的某個字符匹配失配時,模式串下一步應該跳到哪個位置。如模式串中在j 處的字符跟文本串在i 處的字符匹配失配時,下一步用next [j] 處的字符繼續跟文本串i 處的字符匹配,相當於模式串向右移動 j - next[j] 位。

    接下來,分別具體解釋上述3個步驟。

 

3.3 解釋

3.3.1 尋找最長前綴後綴

    如果給定的模式串是:“ABCDABD”,從左至右遍歷整個模式串,其各個子串的前綴後綴分別如下表格所示:

    也就是說,原模式串子串對應的各個前綴後綴的公共元素的最大長度表爲(下簡稱《最大長度表》)

 

 

 

 

rf:

https://www.cnblogs.com/ZuoAndFutureGirl/p/9028287.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章