经典字符串匹配算法——KMP算法
KMP算法
KMP算法是一種高效的字符串匹配算法,在傳統(tǒng)暴力遍歷匹配的基礎(chǔ)上做了一定的優(yōu)化。
首先KMP算法的實(shí)現(xiàn)也是使用了回退思想,不過與暴力遍歷不同,KMP的回退,是讓子串進(jìn)行匹配,而不是主串。
KMP示例
首先我們來看兩個(gè)例子來理解KMP算法:
例1:
分別從str的i和sub的j位置處開始匹配:
此時(shí)a與c不匹配,如果暴力遍歷的話,是i回到到b,j也回到a,重新一輪匹配。而KMP算法,是將子串的j回到第二個(gè)a,str[i]與sub[j]重新開始匹配。原因很明顯,第二個(gè)ab與第一個(gè)ab是相同的,因?yàn)檫@一部分主串與子串是匹配的,所以在主串中也是這樣,因而主串的后半部分的ab就匹配了子串的前半部分ab,就省去了再重新匹配的過程,直接繼續(xù)之前的部分匹配結(jié)果再向后匹配。
繼續(xù)匹配:
再看一個(gè)例2:
開始匹配:
我們看到,此時(shí)a 與 c不匹配,如果暴力遍歷的話,是i回到到b,j也回到a,重新一輪匹配。而KMP算法,是將子串的j回到a,str[i]與sub[j]重新開始匹配。
很多人可能疑問,為什么是這樣?好像很有道理的樣子
我們仔細(xì)觀察,在子串中,j所指的c之前,除了最開頭的a,找不到已經(jīng)與主串匹配好了的字符串a(chǎn)b或者是a(要與c相鄰),**注意,是除了最開頭的a。**正是因?yàn)樵谶@段區(qū)間找不到ab或a,所以才從頭再開始遍歷。i不動(dòng),因?yàn)樵趇前面的主串中已經(jīng)找不到與abc(子串的前三個(gè)字符)匹配的字符串了。
j回到最開始后,再開始匹配:
與之前一樣,在j所指的d之前,除了最開頭的a,找不到與主串已經(jīng)匹配好了的abc或ab或a,所以j又要重新回頭,到最開始。
再次遍歷:
KMP核心
看完上述過程后,我們要實(shí)現(xiàn)這種讓子串回頭的方法,就是定義一個(gè)next數(shù)組,里面對(duì)應(yīng)子串中每一個(gè)字符出現(xiàn)不匹配情況時(shí),要回頭到達(dá)的位置。
KMP 的精髓就是 next 數(shù)組:也就是用 next[j] = k;來表示,每個(gè)j 都對(duì)應(yīng)一個(gè) K 值, 這個(gè) K 就是將來j要移動(dòng)時(shí),要移動(dòng)到的位置。
而 K 的值是這樣求的:
1、規(guī)則:找到匹配成功部分的兩個(gè)相等的真子串(不包含本身),一個(gè)以下標(biāo) 0 字符開始,另一個(gè)以 j-1 下標(biāo)字符結(jié)尾。
2、不管什么數(shù)據(jù),next[0] = -1;next[1] = 0;
3、如果找得到,則k值等于相等的子串的長(zhǎng)度;如果找不到,則k值等于0,即回到a
以例1中的子串為例:ababc
我們默認(rèn)第一個(gè)和第二個(gè)字符出現(xiàn)不匹配情況時(shí),next數(shù)組中存的值分別是-1和0。即,a不匹配時(shí),k == -1,則需要主串的i向后走一步(這里先解釋,可以到代碼實(shí)現(xiàn)的時(shí)候再理解),b不匹配時(shí),回到a重新匹配。
第三個(gè)字符a如果出現(xiàn)不匹配情況,我們找已經(jīng)匹配的相等的兩個(gè)子串:以a開頭的字符串,與以b結(jié)尾的字符串,很明顯找不到,所以它要回到a,k = 0
第四個(gè)字符b出現(xiàn)不匹配情況時(shí),同樣找已經(jīng)匹配的相等的兩個(gè)子串:以a開頭的字符串,以a結(jié)尾的字符串,可以找到字符串a(chǎn),所以k = 1,即回到b
第四個(gè)字符c出現(xiàn)不匹配情況時(shí),找已經(jīng)匹配的相等的兩個(gè)子串:以a開頭的字符串,以a結(jié)尾的字符串,可以找到ab,所以k = 2,即回到第二個(gè)a
至此,子串的next數(shù)組就為:[-1, 0, 0, 1, 2]
兩道求next數(shù)組的練習(xí):
練習(xí) 1: ”ababcabcdabcde”
next數(shù)組:-1 0 0 1 2 0 1 2 0 0 1 2 0 0
練習(xí) 2: ”abcabcabcabcdabcde”
next數(shù)組:-1 0 0 0 1 2 3 4 5 6 7 8 9 0 1 2 3 0
建議大家自主完成,這有助于我們后面的理解
next數(shù)組求解
那么,如何用代碼求出next數(shù)組呢?
求sub[j]的k值
1、當(dāng)sub[j-1] == sub[k]時(shí)(k為sub[j-1]的k值)
同樣以ababc為例
我們已知前面四個(gè)字符的next數(shù)組為:[-1, 0, 0, 1 ],求c的k值
如果按照上面的求法,我們知道k = 2,但仔細(xì)觀察,c前面的b,即sub[j - 1],與第二個(gè)字符b,即sub[k]相同(該k為前一個(gè)字符b的k值),第二個(gè)b之前已經(jīng)匹配的子串a(chǎn)ba中,兩個(gè)符合要求的相等的子串為a,而現(xiàn)在的匹配的子串為abab,這兩個(gè)b相等,在前面字符k值的基礎(chǔ)上,c的k值就可以簡(jiǎn)單地看成是前一個(gè)字符的k + 1,即c的k == 1+1 = 2
2、當(dāng)sub[j-1] != sub[k]時(shí)
以ababcd為例
我們已知ababcd的前五個(gè)字符的next數(shù)組:[-1, 0, 0, 1, 2],求d的k值
很明顯,在已經(jīng)匹配的子串a(chǎn)b中,找不到相等的兩個(gè)子串:以a開頭的字符串,以c結(jié)尾的字符串。所以d要回退到最開始的a處。
此時(shí)的j指向d,且sub[j - 1] != sub[k],即c != a,d應(yīng)該回退到元素sub[k] (即第二個(gè)a)的k值處,也就是d的k == next[k] = 0
代碼實(shí)現(xiàn):
1、求出next數(shù)組
分兩種情況:
還有一種情況之前我們沒有提到,就是回退到了最開始元素的k值處,即-1.此時(shí)說明主串中的字符與j及j之前的所有字符串都不匹配,所以需要向后走一步,從下一個(gè)字符再重新開始匹配
2、匹配邏輯的實(shí)現(xiàn)
與暴力求解類似,只不過當(dāng)字符不相等時(shí),不是雙方都回頭,而是子串回頭。正因?yàn)樽哟畷?huì)回退,當(dāng)回退的下標(biāo)j == -1時(shí),表明主串需要向后走。
完整代碼:
#include<iostream> #include<string> #include<vector>using namespace std;void GetNext(vector<int>& next, const string& subStr, int n) {next[0] = -1;next[1] = 0;int i = 2;int k = 0;//表示i的前一個(gè)元素的next數(shù)組元素值while (i < n){//可能回退至首元素了,說明主串需要向后走,子串從首元素開始,所以也是k = k + 1if (k == -1 || subStr[i - 1] == subStr[k])//P[i - 1] == P[k]{k = k + 1;next[i] = k;i++;}else//不匹配則回退{k = next[k];}}}int KMP(const string& str, const string& subStr, int pos)//從主串的str位置開始匹配 {int strLength = str.size();int subLength = subStr.size();if (str.empty() || subStr.empty()) { return -1; }if (pos >= strLength || pos < 0) { return -1; }vector<int> next(subLength);//子串的next數(shù)組GetNext(next, subStr, subLength);int stri = pos;//遍歷strint subj = 0;//遍歷subStrwhile (stri < strLength && subj < subLength){//回退至子串第一個(gè)元素還未匹配,或者正常匹配,都需要將兩個(gè)坐標(biāo)+1if (subj == -1 || str[stri] == subStr[subj]){stri++;subj++;}else//不匹配了,回退{subj = next[subj];}}if (subj == subLength)//說明匹配到位了{//返回主串的起始匹配位置return stri - subLength + 1;}return -1; }next數(shù)組的優(yōu)化
以子串a(chǎn)aaaaaaab為例,它的next數(shù)組為:[-1, 0, 1, 2, 3, 4, 5, 6, 7]當(dāng)主串字符str[i] != 子串中的最后一個(gè)a時(shí),匹配的字符回退到下標(biāo)6的字符a,此時(shí)str[i]還是 != a,所以依舊需要回退,一直回退到第一個(gè)字符a
對(duì)于這種情況,我們可以做一個(gè)優(yōu)化,當(dāng)回退的字符s2與當(dāng)前字符s1相同,則繼續(xù)回退至s2的k值處,一直回退到不相等或者最開始處。所以s1的k值就等于s2的k值。
練習(xí):模式串 t=‘a(chǎn)bcaabbcabcaabdab’ ,該模式串的 next 數(shù)組的值為( D ) , nextval 數(shù)組的值為 (F)。
A. 0 1 1 1 2 2 1 1 1 2 3 4 5 6 7 1 2 B. 0 1 1 1 2 1 2 1 1 2 3 4 5 6 1 1 2
C. 0 1 1 1 0 0 1 3 1 0 1 1 0 0 7 0 1 D. 0 1 1 1 2 2 3 1 1 2 3 4 5 6 7 1 2
E. 0 1 1 0 0 1 1 1 0 1 1 0 0 1 7 0 1 F. 0 1 1 0 2 1 3 1 0 1 1 0 2 1 7 0 1
注意:這里是將第一個(gè)元素和第二個(gè)元素的初始k值設(shè)置為0和1,所以我們只要把求出的結(jié)果+1即可
總結(jié)
以上是生活随笔為你收集整理的经典字符串匹配算法——KMP算法的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: jrtplib for android,
- 下一篇: 骇基-黑客攻防实战入门⑴