資料結構 筆記:KMP子串查詢演算法
阿新 • • 發佈:2018-11-14
發現
-匹配失敗時的右移位數與子串本身相關,與目標串無關
-移動位數=已匹配的字元數-對應的部分匹配值
-任意子串都穿在一個唯一的部位匹配表
字首
-除了最後一個字元以外,一個字串的全部頭部組合
字尾
-出了第一個字元以外,一個字串的全部尾部組合
部分匹配值
-字首和字尾最長共有元素的長度
字元 | 字首 | 字尾 | 交集 | 匹配 | |
1 | A | 空 | 空 | 空 | 0 |
2 | AB | A | B | 空 | 0 |
3 | ABC | A,AB | BC,C | 空 | 0 |
4 | ABCD | A,AB,ABC | BCD,CD,D | 空 | 0 |
5 | ABCDA | A,AB,ABC,ABCD | BCDA,CDA,DA,A | A | 1 |
6 | ABCDAB | A,AB,ABC,ABCD,ABCDA | BCDAB,CDAB,DAB,AB,B | AB | 2 |
7 | ABCDABD | A,AB,ABC,ABCD,ABCDA, ABCDAB |
BCDABD,CDABD,DABD,ABD,BD,D | 空 | 0 |
實現關鍵
-PMT[1] = 0(下標為0的元素匹配值為0)
-從2個字元開始遞推(從下標為1的字元開始遞推)
-假設PMT[n] = PMT[n-1] +1(最長共有元素的長度)
-當假設不成立,PMT[n] 在PMT[n-1]的基礎上減小
部分匹配表的使用(KMP演算法)
#include <iostream> int* make_pmt(const char* p) { int len = strlen(p); int* ret = static_cast<int*>(malloc(sizeof(int) * len)); if( ret != NULL) { int ll = 0; ret[0] = 0; for(int i = 1;i<len;i++) { while((ll > 0) && p[ll] != p[i]) { ll = ret[ll -1]; } if(p[ll] == p[i]) { ll++; } ret[i] = ll; } } return ret; } int kmp(const char* s,const char* p) { int ret = -1; int sl = strlen(s); int pl = strlen(p); int* pmt = make_pmt(p); if((pmt != NULL)&&(0 < pl) &&(pl <= sl)) { for(int i = 0,j = 0;i<sl;i++) { while((j > 0) && (s[i] != p[j])) { j = pmt[j-1]; } if(s[i] == p[j]) { j++; } if( j == pl ) { ret = i + 1 - pl; break; } } } free(pmt); return ret; } int main() { cout << kmp("sfshdfuweihrfwshfuiwehfuwefiwhe","sfshdfuweihrfwshfuiwehfuwefiwhes") << endl; return 0; }
總結:
-部分匹配表示提高子串查詢效率的關鍵
-部分匹配值定義為字首和字尾最長共有元素的長度
-可以用遞推的方法產生部分匹配表
-KMP利用部分匹配值與子串移動位數的關係提高查詢效率