字串匹配的演算法當屬KMP最為著名了,人人皆知,但是KMP演算法是如何做到高效率字串匹配的呢?
我們首先來看看一般的暴力的字串匹配演算法,對於串s和模式串pattern,依次枚舉s中的每一個字元作為起點與pattern嘗試進行匹配,直到遇到不匹配的字元的時候,取下一個s中的字元作為起點與模式串pattern重新進行匹配。我們知道這樣的時間複雜度是O(n*m)的,顯然效率很不好。
從上面暴力匹配的過程我們可以知道,當暴力進行匹配的時候,遇到不匹配的字元,不一定要從s的下一個字元重新與模式串進行匹配,那麼我們如何做到這一點呢?這就要用到KMP中神奇的next數組了。
next數組記錄的是模式串的特徵,從而當匹配不成功的時候,我們不一定完全對s的下一個字元與模式串從頭開始匹配。於是next[i]表示當i與s中的某個串匹配不成功的時候,我們應該用第next[i]的字元與s中的當前位進行匹配。也就是說模式串pattern的子串[pattern[0], pattern[next[k]-1]]與子串[pattern[k-next[k]],
pattern[k-1]]完全相同。
我們為什麼能將next數組用在與s的匹配過程中呢?因為當s[i]與patter[k]不匹配的時候,說明pattern的子串[pattern[0], pattern[k-1]]已經與s的子串[s[i-k], s[i-1]]完全符合了,而這個時候藉助next數組,我們知道如果pattern有一個首碼與s[0, i-1]的某一個尾碼完全相同,我們就應該將首碼的最後一個字元的後面一個字元同s[i]進行嘗試匹配。這樣子大大的減少了沒有作用的盲目匹配嘗試。
我們可以預見KMP演算法的均攤複雜度是O(n+m),為什麼呢?因為你的s串是不會回退的,因此最多訪問了n次,而模式串pattern在每一次匹配中的走動均攤下來近似為O(m)的,因此總的複雜度為O(n+m)。
下面貼上我的KMP演算法的模板,如果模式串在s中出現,則返回子串第一次出現的位置,否則返回-1。
#include <iostream>#include <cstdlib>#include <cstdio>#include <cstring>using namespace std;const int kMax1 = 1000010;const int kMax2 = 10010;char g_pattern[kMax2];char g_s[kMax1];int g_next[kMax2];void GetNext(int n){ memset(g_next, -1, sizeof(g_next)); g_next[0] = -1; g_next[1] = 0; int k = 0; int i = 1; while(i<(n-1)) { //printf("i=%d, k=%d\n", i, k); if(k == -1 || g_pattern[k] == g_pattern[i]) { ++i; ++k; g_next[i] = k; } else k = g_next[k]; }}int KMP(int n){ int ans = -1; int i = 0; int j = 0; int pattern_len = strlen(g_pattern); while(i < n) { if(j == -1 || g_s[i] == g_pattern[j]) { ++i; ++j; } else j = g_next[j]; if(j == pattern_len) { ans = i - pattern_len; break; } } return ans;}
該篇博文關於KMP演算法的理解和代碼,都是自己參照資料結構與演算法的書上面的一點理解。而代碼之前自己寫的方式不一樣,為了規範和容易閱讀,參考了網上的代碼,這個代碼肯定是沒有問題的,初學者可以放心的拿去當做模板使用。
最後如果牛人發現了本博文存在紕漏,望牛人不吝賜教,我定當認真改過。