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繼續跟文本串匹配。
- 2. 下面的問題是:已知next [0, ..., j],如何求出next [j + 1]呢?
對於P的前j+1個序列字符:
- 若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串后綴的匹配。

模式串的后綴:ABDE
模式串的前綴:ABC
前綴右移兩位: ABC
- void GetNext(char* p,int next[])
- {
- int pLen = strlen(p);
- next[0] = -1;
- int k = -1;
- int j = 0;
- while (j < pLen - 1)
- {
- //p[k]表示前綴,p[j]表示后綴
- if (k == -1 || p[j] == p[k])
- {
- ++k;
- ++j;
- next[j] = k;
- }
- else
- {
- k = next[k];
- }
- }
- }
用代碼重新計算下“ABCDABD”的next 數組,以驗證之前通過“最長相同前綴后綴長度值右移一位,然后初值賦為-1”得到的next 數組是否正確,計算結果如下表格所示:
從上述表格可以看出,無論是之前通過“最長相同前綴后綴長度值右移一位,然后初值賦為-1”得到的next 數組,還是之后通過代碼遞推計算求得的next 數組,結果是完全一致的。
3.3.5 基於《next 數組》匹配
下面,我們來基於next 數組進行匹配。
還是給定文本串“BBC ABCDAB ABCDABCDABDE”,和模式串“ABCDABD”,現在要拿模式串去跟文本串匹配,如下圖所示:
在正式匹配之前,讓我們來再次回顧下上文2.1節所述的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 值,即移動的實際位數為: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 位。
- 6. 匹配成功,過程結束。
匹配過程一模一樣。也從側面佐證了,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。