設主串(下文中咱們稱做s)爲:"a b a c a a b a c a b a b b"
模式串(下文中咱們稱爲w)爲:"a b a c a b"
用暴力匹配字符串的過程當中,咱們會把s[0]和t[0]匹配,若是相同則匹配下一個字符,直到出現不相同的狀況,此時咱們會丟棄前面的匹配信息,而後把s[1] 跟 t[0]匹配,循環進行,直到主串結束,或者出現匹配成功的狀況。這種丟棄前面的匹配信息的方法,極大地下降了匹配效率。以下圖所示:
![]()
而KMP算法在匹配失敗時,主串不回溯,即i值保持不變,模式串向右移動j - next[j] (next數組的求法和爲啥這樣移動後面會詳細介紹,這裏能夠先混個眼熟~),j變成了next[j],下一次匹配時,s[i]和t[j]繼續進行比較,直到匹配成功,具體過程以下圖所示:
![]()
仍是以圖2中的s串和t串爲例,當KMP第一次匹配失敗時,i不變,j變成了next[j],而後第二次匹配時,s[i]繼續和t[j]進行匹配,從圖2能夠看出,KMP第二次匹配時,是直接從t[1]開始匹配的,那如何保證t[1]以前的串和s串中的對應元素相同呢?具體問題以下圖所示:
![]()
顯然,這裏用到了模式串t自身的內部匹配信息,t的next數組以下:
![]()
當KMP第一次匹配失敗時,i = 5, j = 5, 此時 根據t的自身內部匹配信息和與s的局部匹配信息, 將t的子串的前綴移動到後綴的位置,移動距離以下圖所示,而後下一次匹配繼續從s[i]和t[j]開始:
![]()
1.next數組的求法html
vector<int> getNext(string t) { // j表示當前位置的next值,具體含義是當前子串前綴和後綴相同的最大位數,初始化爲-1 int i = 0, j = -1, n = t.size(); vector<int> next(n); next[0] = -1; while(i < n) { //當j = -1時表示當前子串沒有相等的前綴和後綴 //當t[i] = t[j]表示當前子串前綴的最後一位與後綴的最後一位相等, //此時,下一個next值爲爲當前next值加1(相似於dp的遞推關係) if(j == -1 || t[i] == t[j]) { i++; j++; // next[i] = j; } // 若是匹配不成功,須要將已經匹配的前綴長度進行回退, // 直到直到適合的匹配長度 else j = next[j]; } return next; }
2.KMP匹配過程算法
int KMP(string s, string t) { if(s.empty() || s.size() < t.size()) return -1; int i = 0, j = 0, m = s.size(), n = t.size(); // 求t的next數組 vector<int> next = getNext(t); while(i < m && j < n) { // 當j = -1說明j已經回退到模式串的起始位置,沒法再次向前回退 // 此時,須要將i移動到下一個位置,繼續將s[i]和t[0]進行匹配 // 當s[i] = t[j]時,當前位匹配成功,兩個指針同時向後移動 if(j == -1 || s[i] == t[j]) { i++; j++; } // 當兩個字符不相等時,依據模式串的next數組,將指針j回退到next[j] // 至關於將模式串t右移j-next[j],而後繼續將s[i]和t[j]進行匹配 else j = next[j]; } // 匹配成功,起始位置位i-j,不然返回-1,表示匹配失敗 return j == n ? i - j : -1; }
KMP算法的改進是由朱洪大佬完成的,他主要改進了next數組的求法,讓主串和模式串匹配失敗時,模式串移動更快。咱們再來觀察圖2,當KMP第一次匹配失敗時,s[i] = 'a', t[j] = 'b',模式串t向右移動後,第二次匹配時再比較s[i]和t[j], 驚人地發現!!!移動後的t[j]仍然是'b',即t[j] = t[next[j]],顯然,因爲以前t[j]和s[i]已經匹配失敗,再換一個和t[j]相同的t[next[j]]再比較,確定失敗,這就至關於多了一次毫無心義的比較。所以,再計算next值以前,先判斷t[j] = t[next[j]]是否成立,若是成立,回退next值,讓next[i] = next[j],此時再向右移動模式串時,就能夠直接跳過這一次毫無心義的比較,代碼以下:
vector<int> getNextVal(string t) { int i = 0, j = -1, n = t.size(); // 通常習慣把改進後的next數組稱爲nextVal vector<int> nextVal(n); next[0] = -1; while (i < n) { if (j == -1 || t[i] == t[j]) { i++; j++; // 若是下一個元素和它的next值所在的元素相等,回退它的next值 if (t[i] == t[j]) next[i] = next[j]; else next[i] = j; } else j = next[j]; } return next; }
咱們用攤還分析來看KMP算法:
關於匹配指針的位置cur
操做A: 匹配時,cur++;
操做B: 失配時, cur = next[cur],這個next[cur] <= cur是成立的。
根據勢能分析(cur >= 0恆成立),咱們能夠證實,操做A的次數必定比操做B的次數要多,兩個操做都是O(1)。而操做A的執行次數很容易分析最壞上界是O(n)。那麼O(n) = T(A) >= T(B), 所以匹配的時間複雜度T(A+B) = O(n)。
對攤還分析還有疑問的看參考 https://www.cnblogs.com/elpsy...
我是lioney,年輕的後端攻城獅一枚,愛鑽研,愛技術,愛分享。
我的筆記,整理不易,感謝閱讀、點贊和收藏。
文章有任何問題歡迎你們指出,也歡迎你們一塊兒交流後端各類問題!