資料結構- KMP
阿新 • • 發佈:2018-12-07
資料結構 - KMP
引言 & 介紹
- 由於李總說過串這一章只講一個KMP, 所以我這裡也就只說一個KMP演算法了
- KMP演算法, 說得簡單點就是關鍵字搜尋
一般方法
- 一般的關鍵字搜尋的演算法為:
1 int Search(string a, string b) { 2 int lena = a.length(); 3 int lenb = b.length(); 4 int i = 0, j = 0; 5 6 while(i < lena && j < lenb) { 7 if (a[i] == b[j]) { 8 i++; j++; 9 } else { 10 i = i - j + 1; 11 j = 0; 12 } 13 } 14 return j == lenb ? i - j + 1 : -1; 15 }
- 這個方法的時間複雜度為O(lena * lenb), 可以說是相當高了, 我們可能覺得無所謂, 但是大牛們就會想一種新的演算法來優化, 這就是KMP演算法, KMP演算法的時間複雜度為O(lena + lenb)
KMP演算法
KMP函式
- 如果主串a為“abcdabceabcdabcd”, 模式串b為“abcdabcd”, 一般方法在j = 7(從0開始)時就失配了, 但是, 一般方法就會執行很多無意義的部分, 現在, 我們把程式碼做一點修改:
int KMP(string a, string b) { int lena = a.length(), lenb = b.length(); int i = 0, j = 0; int *next = GetNext(b); while(i < lena && j < lenb) { if (j == -1 || a[i] == b[j]) { i++; j++; } else { j = next[j]; } } return j == lenb ? i - j + 1 : -1; }
求next陣列
- 這個程式碼, 當然是不完整的, 所以這個先看下面這個求next陣列的程式碼:
1 int* GetNext(string b) { 2 int j = 0, k = -1; 3 int len = b.length(); 4 int *next = new int[len + 1]; 5 next[0] = -1; 6 7 while (j < len) { 8 if (k == -1 || b[j] == b[k]) { 9 j++; 10 k++; 11 next[j] = k; 12 } else { 13 k = next[k]; 14 } 15 } 16 return next; 17 }
分析next陣列
- 分析next陣列前, 我們再說說主串a為“abcdabceabcdabcd”, 模式串b為“abcdabcd”的情況
- 當j = 7的時候a[7] 和 b[7]不匹配, 但是a[0 - 6]和b[0 - 6]是匹配的, 如果b[0 - 6]中的前x個字元和後x個字元一模一樣, 那麼, 我們就無需把j置零, 只需要把j置為x, 直接比較b[x]和a[7]就可以了
- 而事實上, b[0 - 2]和b[4 - 6]是一樣的, 這個x為3, 也可以直接看出, a[4 - 6]和b[0 - 2]是匹配的, 所以我們直接看a[7]和ab[3], 當然啦, 這裡a[7] = e, b[3] = d也不匹配, 所以再執行這個步驟·······
- 上面的x就是next[j], 在GetNext函式裡面則是next[k]
- 現在應該知道next陣列的作用了吧, next[j]表示, 在a[i]與b[j]不匹配時, b的前next[j]個字元, 和後next[j]個字元完全相同
- 知道了next陣列的含義之後, 再看上面的函式應該就比較好理解了
GetNext的優化
- 現在考慮a = “aaabaaabaaab”, b = “aaaa”的情況
- next陣列為{-1, 0, 1, 2, 3}, 這就意味著, j = 3時失配時, j會從3減到-1, 而不是直接從3跳到-1, 如此完美的演算法在這一步出了這種情況豈不可惜, 所以, 這個演算法還有最後一步優化
1 int* GetNext(string b) { 2 int j = 0, k = -1; 3 int len = b.length(); 4 int *next = new int[len + 1]; 5 next[0] = -1; 6 7 while (j < len) { 8 if (k == -1 || b[j] == b[k]) { 9 if (b[++j] == b[++k]) { 10 next[j] = next[k]; 11 } else { 12 next[j] = k; 13 } 14 } else { 15 k = next[k]; 16 } 17 } 18 return next; 19 }
- 將GetNext略作修改就好了
程式碼
- 下面給出完整的KMP演算法的程式碼
1 #include<bits/stdc++.h> 2 using namespace std; 3 4 int* GetNext(string b) { 5 int j = 0, k = -1; 6 int len = b.length(); 7 int *next = new int[len + 1]; 8 next[0] = -1; 9 10 while (j < len) { 11 if (k == -1 || b[j] == b[k]) { 12 if (b[++j] == b[++k]) { 13 next[j] = next[k]; 14 } else { 15 next[j] = k; 16 } 17 } else { 18 k = next[k]; 19 } 20 } 21 return next; 22 } 23 24 int KMP(string a, string b) { 25 int lena = a.length(), lenb = b.length(); 26 int i = 0, j = 0; 27 int *next = GetNext(b); 28 29 while (i < lena && j < lenb) { 30 if (j == -1 || a[i] == b[j]) { 31 i++; j++; 32 } else { 33 j = next[j]; 34 } 35 } 36 return j == lenb ? i - j + 1 : -1; 37 } 38 39 int main () { 40 string a, b; 41 while (cin >> a >> b) { 42 cout << KMP(a, b) << endl; 43 } 44 return 0; 45 }