總結:
- 生成和原長度相同的數組Nextarr, 每個元素的值爲原來數組此下標前最長重複子串的長度。
- 利用Nextarr快速跳過包含相同重複子串部分
如
- str 1=‘abcdabce’
- Nextarr = [-1,0,0,0,0,1,2,3,0]#目的是記錄最大重複子串,後面匹配直接跳過
- str2 ='abce'
一個經典的例子,是判斷一個樹是否是另外一棵樹的子樹,或者是子結構(注意區分子樹和子結構)
- 先序列化兩顆數
- 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 個字符。
轉換成代碼表示,則是:
- int KmpSearch(char* s, char* p)
- {
- int i = 0;
- int j = 0;
- int sLen = strlen(s);
- int pLen = strlen(p);
- while (i < sLen && j < pLen)
- {
- //①如果j = -1,或者當前字符匹配成功(即S[i] == P[j]),都令i++,j++
- if (j == -1 || s[i] == p[j])
- {
- i++;
- j++;
- }
- else
- {
- //②如果j != -1,且當前字符匹配失敗(即S[i] != P[j]),則令 i 不變,j = next[j]
- //next[j]即爲j所對應的next值
- j = next[j];
- }
- }
- if (j == pLen)
- return i - j;
- else
- return -1;
- }
繼續拿之前的例子來說,當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: