轉載請註明來源,幷包含相關連接。html
網上有不少講解KMP算法的博客,我就不浪費時間再寫一份了。直接推薦一個當初我入門時看的博客吧:
http://www.cnblogs.com/yjiyjige/p/3263858.html
這位同窗用詳細的圖文模式講解了KMP算法,很是適合入門。
----------------------------------------------------------------------------------------------面試
KMP的next數組求法是很不容易搞清楚的一部分,也是最重要的一部分。我這篇文章就以我本身的感悟來慢慢推導一下吧!保證你看完事後是知其然,也知其因此然。算法
若是你還不知道KMP是什麼,請先閱讀上面的連接,先搞懂KMP是要幹什麼。
下面咱們就來講說KMP的next數組求法。
KMP的next數組簡單來講,假設有兩個字符串,一個是待匹配的字符串strText,一個是要查找的關鍵字strKey。如今咱們要在strText中去查找是否包含strKey,用i來表示strText遍歷到了哪一個字符,用j來表示strKey匹配到了哪一個字符。
若是是暴力的查找方法,當strText[i]和strKey[j]匹配失敗的時候,i和j都要回退,而後從i-j的下一個字符開始從新匹配。
而KMP就是保證i永遠不回退,只回退j來使得匹配效率有所提高。它用的方法就是利用strKey在失配的j爲以前的成功匹配的子串的特徵來尋找j應該回退的位置。而這個子串的特徵就是先後綴的相同程度。
因此next數組其實就是查找strKey中每一位前面的子串的先後綴有多少位匹配,從而決定j失配時應該回退到哪一個位置。數組
我知道上面那段廢話很難懂,下面咱們看一個彩圖:優化
這個圖畫的就是strKey這個要查找的關鍵字字符串。假設咱們有一個空的next數組,咱們的工做就是要在這個next數組中填值。
下面咱們用數學概括法來解決這個填值的問題。
這裏咱們借鑑數學概括法的三個步驟(或者說是動態規劃?):
一、初始狀態
二、假設第j位以及第j位以前的咱們都填完了
三、推論第j+1位該怎麼填spa
初始狀態咱們稍後再說,咱們這裏直接假設第j位以及第j位以前的咱們都填完了。也就是說,從上圖來看,咱們有以下已知條件:
next[j] == k;
next[k] == 綠色色塊所在的索引;
next[綠色色塊所在的索引] == 黃色色塊所在的索引;
這裏要作一個說明:圖上的色塊大小是同樣的(沒騙我?好吧,請忽略色塊大小,色塊只是表明數組中的一位)。3d
咱們來看下面一個圖,能夠獲得更多的信息:code
1.由"next[j] == k;"這個條件,咱們能夠獲得A1子串 == A2子串(根據next數組的定義,先後綴那個)。htm
2.由"next[k] == 綠色色塊所在的索引;"這個條件,咱們能夠獲得B1子串 == B2子串。blog
3.由"next[綠色色塊所在的索引] == 黃色色塊所在的索引;"這個條件,咱們能夠獲得C1子串 == C2子串。
4.由1和2(A1 == A2,B1 == B2)能夠獲得B1 == B2 == B3。
5.由2和3(B1 == B2, C1 == C2)能夠獲得C1 == C2 == C3。
6.B2 == B3能夠獲得C3 == C4 == C1 == C2
上面這個就是很簡單的幾何數學,仔細看看都能看懂的。我這裏用相同顏色的線段表示徹底相同的子數組,方便觀察。
接下來,咱們開始用上面獲得的條件來推導若是第j+1位失配時,咱們應該填寫next[j+1]爲多少?
next[j+1]便是找strKey從0到j這個子串的最大先後綴:
#:(#:在這裏是個標記,後面會用)咱們已知A1 == A2,那麼A1和A2分別日後增長一個字符後是否還相等呢?咱們得分狀況討論:
(1)若是str[k] == str[j],很明顯,咱們的next[j+1]就直接等於k+1。
用代碼來寫就是next[++j] = ++k;
(2)若是str[k] != str[j],那麼咱們只能從已知的,除了A1,A2以外,最長的B1,B3這個先後綴來作文章了。
那麼B1和B3分別日後增長一個字符後是否還相等呢?
因爲next[k] == 綠色色塊所在的索引,咱們先讓k = next[k],把k挪到綠色色塊的位置,這樣咱們就能夠遞歸調用"#:"標記處的邏輯了。
因爲j+1位以前的next數組咱們都是假設已經求出來了的,所以,上面這個遞歸總會結束,從而獲得next[j+1]的值。
咱們惟一欠缺的就是初始條件了:
next[0] = -1, k = -1, j = 0
另外有個特殊狀況是k爲-1時,不能繼續遞歸了,此時next[j+1]應該等於0,即把j回退到首位。
即 next[j+1] = 0; 也能夠寫成next[++j] = ++k;
public static int[] getNext(String ps) { char[] strKey = ps.toCharArray(); int[] next = new int[strKey.length]; // 初始條件 int j = 0; int k = -1; next[0] = -1; // 根據已知的前j位推測第j+1位 while (j < strKey.length - 1) { if (k == -1 || strKey[j] == strKey[k]) { next[++j] = ++k; } else { k = next[k]; } } return next; }
如今再看這段代碼應該沒有任何問題了吧。
優化:
細心的朋友應該發現了,上面有這樣一句話:
(1)若是str[k] == str[j],很明顯,咱們的next[j+1]就直接等於k+1。用代碼來寫就是next[++j] = ++k;
但是咱們知道,第j+1位是失配了的,若是咱們回退j後,發現新的j(也就是此時的++k那位)跟回退以前的j也相等的話,必然也是失配。因此還得繼續往前回退。
public static int[] getNext(String ps) { char[] strKey = ps.toCharArray(); int[] next = new int[strKey.length]; // 初始條件 int j = 0; int k = -1; next[0] = -1; // 根據已知的前j位推測第j+1位 while (j < strKey.length - 1) { if (k == -1 || strKey[j] == strKey[k]) { // 若是str[j + 1] == str[k + 1],回退後仍然失配,因此要繼續回退 if (str[j + 1] == str[k + 1]) { next[++j] = next[++k]; } else { next[++j] = ++k; } } else { k = next[k]; } } return next; }
好了,自此KMP的next求法所有講解完畢。歡迎你們指出文章的錯誤,我好更加完善它。
----------------------------------------------------------------------------------------------------------
下面說說面試的時候,給一個字符串,要你寫出它的Next數組,應該怎麼寫:
①:先對每一位左邊的子串求出最大先後綴串的長度,做爲初始的Next數組
②:由於第一位失配時須要移動i,所以賦值爲-1
③:P[3] == A, Next[3] == 0, P[0] == A; 因此P[3] == P[0], (移動過去後仍是失配,須要繼續移動),優化Next[3]爲Next[0],即-1
④:同理優化Next[10]爲Next[0],即-1
⑤:同理優化P[14],P[15],P[16]