1. 程式人生 > >資料結構- KMP

資料結構- KMP

資料結構 - 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 }