next數組算法
- 1. 若是對於值k,已有p0 p1, ..., pk-1 = pj-k pj-k+1, ..., pj-1,至關於next[j] = k。
- 此意味着什麼呢?究其本質,next[j] = k 表明p[j] 以前的模式串子串中,有長度爲k 的相同前綴和後綴。有了這個next 數組,在KMP匹配中,當模式串中j 處的字符失配時,下一步用next[j]處的字符繼續跟文本串匹配,至關於模式串向右移動j - next[j] 位。
舉個例子,以下圖,根據模式串「ABCDABD」的next 數組可知失配位置的字符D對應的next 值爲2,表明字符D前有長度爲2的相同前綴和後綴(這個相同的前綴後綴即爲「AB」),失配後,模式串須要向右移動j - next [j] = 6 - 2 =4位。數組

向右移動4位後,模式串中的字符C繼續跟文本串匹配。spa
- 2. 下面的問題是:已知next [0, ..., j],如何求出next [j + 1]呢?
對於P的前j+1個序列字符:.net
- 若p[k] == p[j],則next[j + 1 ] = next [j] + 1 = k + 1;
- 若p[k ] ≠ p[j],若是此時p[ next[k] ] == p[j ],則next[ j + 1 ] = next[k] + 1,不然繼續遞歸前綴索引k = next[k],然後重複此過程。 至關於在字符p[j+1]以前不存在長度爲k+1的前綴"p0 p1, …, pk-1 pk"跟後綴「pj-k pj-k+1, …, pj-1 pj"相等,那麼是否可能存在另外一個值t+1 < k+1,使得長度更小的前綴 「p0 p1, …, pt-1 pt」 等於長度更小的後綴 「pj-t pj-t+1, …, pj-1 pj」 呢?若是存在,那麼這個t+1 即是next[ j+1]的值,此至關於利用已經求得的next 數組(next [0, ..., k, ..., j])進行P串前綴跟P串後綴的匹配。
通常的文章或教材可能就此一筆帶過,但大部分的初學者可能仍是不能很好的理解上述求解next 數組的原理,故接下來,我再來着重說明下。
以下圖所示,假定給定模式串ABCDABCE,且已知next [j] = k(至關於「p0 pk-1」 = 「pj-k pj-1」 = AB,能夠看出k爲2),現要求next [j + 1]等於多少?由於pk = pj = C,因此next[j + 1] = next[j] + 1 = k + 1(能夠看出next[j + 1] = 3)。表明字符E前的模式串中,有長度k+1 的相同前綴後綴。
但
若是pk != pj 呢?說明「p0 pk-1 pk」 ≠ 「pj-k pj-1 pj」。換言之,當pk != pj後,字符E前有多大長度的相同前綴後綴呢?很明顯,由於C不一樣於D,因此ABC 跟 ABD不相同,即字符E前的模式串沒有長度爲k+1的相同前綴後綴,也就不能再簡單的令:next[j + 1] = next[j] + 1 。因此,我們只能去尋找長度更短一點的相同前綴後綴。
結合上圖來說,若能
在前綴
「 p0 pk-1 pk 」 中不斷的遞歸前綴索引k = next [k],找到一個字符pk’ 也爲D,表明pk’ = pj,且知足p0 pk'-1 pk' = pj-k' pj-1 pj,則最大相同的前綴後綴長度爲k' + 1,從而next [j + 1] = k’ + 1 = next [k' ] + 1。不然前綴中沒有D,則表明沒有相同的前綴後綴,next [j + 1] = 0。
那爲什麼遞歸前綴索引k = next[k],就能找到長度更短的相同前綴後綴呢?這又歸根到next數組的含義。
咱們拿前綴 p0 pk-1 pk 去跟後綴pj-k pj-1 pj匹配,若是pk 跟pj 失配,下一步就是用p[next[k]] 去跟pj 繼續匹配,若是p[ next[k] ]跟pj仍是不匹配,則須要尋找長度更短的相同前綴後綴,即下一步用p[ next[ next[k] ] ]去跟pj匹配。此過程至關於模式串的自我匹配,因此不斷的遞歸k = next[k],直到要麼找到長度更短的相同前綴後綴,要麼沒有長度更短的相同前綴後綴。以下圖所示:
因此,因最終在前綴ABC中沒有找到D,故E的next 值爲0:
模式串的後綴:ABDE
模式串的前綴:ABC
前綴右移兩位: ABC
讀到此,有的讀者可能又有疑問了,那可否舉一個能在前綴中找到字符D的例子呢?OK,我們便來看一個能在前綴中找到字符D的例子,以下圖所示:
給定模式串DABCDABDE,咱們很順利的求得字符D以前的「DABCDAB」的各個子串的最長相同前綴後綴的長度分別爲0 0 0 0 1 2 3,但當遍歷到字符D,要求包括D在內的「DABCDABD」最長相同前綴後綴時,咱們發現pj處的字符D跟pk處的字符C不同,換言之,前綴DABC的最後一個字符C 跟後綴DABD的最後一個字符D不相同,因此不存在長度爲4的相同前綴後綴。
怎麼辦呢?既然沒有長度爲4的相同前綴後綴,我們能夠尋找長度短點的相同前綴後綴,最終,因在p0處發現也有個字符D,p0 = pj,因此p[j]對應的長度值爲1,至關於E對應的next 值爲1(即字符E以前的字符串「DABCDABD」中有長度爲1的相同前綴和後綴)。
綜上,能夠經過遞推求得next 數組,代碼以下所示:
- void GetNext(char* p,int next[])
- {
- int pLen = strlen(p);
- next[0] = -1;
- int k = -1;
- int j = 0;
- while (j < pLen - 1)
- {
-
- if (k == -1 || p[j] == p[k])
- {
- ++k;
- ++j;
- next[j] = k;
- }
- else
- {
- k = next[k];
- }
- }
- }
用代碼從新計算下「ABCDABD」的next 數組,以驗證以前經過「最長相同前綴後綴長度值右移一位,而後初值賦爲-1」獲得的next 數組是否正確,計算結果以下表格所示:code

從上述表格能夠看出,不管是以前經過「最長相同前綴後綴長度值右移一位,而後初值賦爲-1」獲得的next 數組,仍是以後經過代碼遞推計算求得的next 數組,結果是徹底一致的。blog
3.3.5 基於《next 數組》匹配
下面,咱們來基於next 數組進行匹配。遞歸

仍是給定文本串「BBC ABCDAB ABCDABCDABDE」,和模式串「ABCDABD」,如今要拿模式串去跟文本串匹配,以下圖所示:索引

在正式匹配以前,讓咱們來再次回顧下上文2.1節所述的KMP算法的匹配流程:ip
- 「假設如今文本串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 值,即移動的實際位數爲:j - next[j],且此值大於等於1。」
- 1. 最開始匹配時
- P[0]跟S[0]匹配失敗
- 因此執行「若是j != -1,且當前字符匹配失敗(即S[i] != P[j]),則令 i 不變,j = next[j]」,因此j = -1,故轉而執行「若是j = -1,或者當前字符匹配成功(即S[i] == P[j]),都令i++,j++」,獲得i = 1,j = 0,即P[0]繼續跟S[1]匹配。
- P[0]跟S[1]又失配,j再次等於-1,i、j繼續自增,從而P[0]跟S[2]匹配。
- P[0]跟S[2]失配後,P[0]又跟S[3]匹配。
- P[0]跟S[3]再失配,直到P[0]跟S[4]匹配成功,開始執行此條指令的後半段:「若是j = -1,或者當前字符匹配成功(即S[i] == P[j]),都令i++,j++」。
- 2. P[1]跟S[5]匹配成功,P[2]跟S[6]也匹配成功, ...,直到當匹配到P[6]處的字符D時失配(即S[10] != P[6]),因爲P[6]處的D對應的next 值爲2,因此下一步用P[2]處的字符C繼續跟S[10]匹配,至關於向右移動:j - next[j] = 6 - 2 =4 位。

- 3. 向右移動4位後,P[2]處的C再次失配,因爲C對應的next值爲0,因此下一步用P[0]處的字符繼續跟S[10]匹配,至關於向右移動:j - next[j] = 2 - 0 = 2 位。

- 4. 移動兩位以後,A 跟空格不匹配,模式串後移1 位。

- 5. P[6]處的D再次失配,由於P[6]對應的next值爲2,故下一步用P[2]繼續跟文本串匹配,至關於模式串向右移動 j - next[j] = 6 - 2 = 4 位。

匹配過程如出一轍。也從側面佐證了,next 數組確實是只要將各個最大前綴後綴的公共元素的長度值右移一位,且把初值賦爲-1 便可。字符串
3.3.6 基於《最大長度表》與基於《next 數組》等價
咱們已經知道,利用next 數組進行匹配失配時,模式串向右移動 j - next [ j ] 位,等價於已匹配字符數 - 失配字符的上一位字符所對應的最大長度值。緣由是:
- j 從0開始計數,那麼當數到失配字符時,j 的數值就是已匹配的字符數;
- 因爲next 數組是由最大長度值表總體向右移動一位(且初值賦爲-1)獲得的,那麼失配字符的上一位字符所對應的最大長度值,即爲當前失配字符的next 值。
但爲什麼本文不直接利用next 數組進行匹配呢?由於next 數組很差求,而一個字符串的前綴後綴的公共元素的最大長度值很容易求。例如若給定模式串「ababa」,要你快速口算出其next 數組,乍一看,每次求對應字符的next值時,還得把該字符排除以外,而後看該字符以前的字符串中有最大長度爲多大的相同前綴後綴,此過程不夠直接。而若是讓你求其前綴後綴公共元素的最大長度,則很容易直接得出結果:0 0 1 2 3,以下表格所示:

而後這5個數字 所有總體右移一位,且初值賦爲-1,即獲得其next 數組:-1 0 0 1 2。