C++ : KMP 字符串匹配算法
在傳統字符串匹配中我們求得字符串p出現在字符串s中的位置。我們把字符串s稱為主串,字符串p稱為模式串。
KMP算法的原理簡單來說就是匹配的時候不回溯主串的指針i,而只回溯模式串指針j ,即匹配過程中,不移動主串,指移動模式串來達到"盡可能向右滑動最大的距離"。
| s[num] | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
| 主串s | a | c | a | b | a | a | b | a | a | b | c | a | c | a | a | b | c | ||||||
| 模式串p | a | b | a | a | b | c |
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?圖1.
假如模式串p中的c與s[11] 失配了,那么再傳統匹配算法中,是拿s[7]和p[0] (a)去匹配,而在KMP匹配中是那s[11]和p[2] (a)中去匹配。因為在這次失配中可知,主串s中6~10的值就是abaab,再拿p[0]和s[7] 匹配必定失配,這次比較就顯得多余。那么我們怎么知道失配的時候,模式串要向右移多少個單位呢?
假設圖1此時主串要正在匹配的位置是 i =11 ,而 與 p[j]=='c',即j==5產生了失配,那么j指針要回溯到 某個k,假如=2值時要進行下一步匹配?,那么k值必須滿足兩個關系式
由圖一可知? ?p[3]p[4]=s[9]s[10],
由圖二可知? ?p[0]p[1]=s[9]s[10]
| s[num] | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
| 主串s | a | c | a | b | a | a | b | a | a | b | c | a | c | a | a | b | c | ||||||
| 模式串p | a | b | a |
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? ?圖2.
換成通用表達式就是
即
用邏輯意義上來解釋公式3:就是當在模式串匹配第j個要回溯到k時此時j和k滿足 前綴k個值和后綴k個值要相同。滿足這條件的最大k值即為將要回溯的位置,假如用next數組表示模式串要回溯的位置其中 next[j]=k。
所以實際上,模式串中的next[j]的值與主串s是無關的。
假設next[j]=k即求next[j+1]
如果此時P[j] = P[k]? 那么next[j+1]=k+1 即 next[j+1]= next[j] +1
如果此時 P[j] != P[k] ,那么就要移動next[k]個字符進行比較,達到s[i] = p[k],前綴與后綴對齊的目的。相等則next[j+1]=next[k]+1,不相等則迭代 k=next[k]重新移動比較,例如結合圖3和圖4,next[5]=2,求next[6]?
因為next[5]=2,所以比較出P[5] !=P[2],前綴無法對齊后綴就繼續向右移(回溯),
因為next[2]=0,所以比較出P[5] !=p[0],
因為next[0]=-1接下來比對P[5]和P[-1] 因為next[0] = -1;
當不存在可回溯的k時next[j+1] = -1?;
在存在一個可回溯的位置0<k'<k<j 那么?
?
| s[num] | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
| 主串s | a | c | a | b | a | a | b | a | a | b | c | a | c | a | a | b | c | ||||||
| 模式串p | a | b | a | a | b | c | a |
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?圖3
| s[num] | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
| 主串s | a | c | a | b | a | a | b | a | a | b | c | a | c | a | a | b | c | ||||||
| 模式串p | a | b | a | a | b | c | a | c |
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?圖4.
.
| s[num] | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
| 主串s | a | c | a | b | a | a | b | a | a | b | c | a | c | a | a | b | c | ||||||
| 模式串p | a | b | a | a | b | c | a | c |
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?圖5.
.所以字符串abaabcac的next值為 next [] = {-1,0,0,1,1,2,0,1};
改進的next ,
例如字符串 aaaab的next []={-1,0,1,2,3};
| s[num] | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
| 主串s | |||||||||||||||||||||||
| 模式串p | a | a | a | a | b |
? ? ? KMP算法中當s[9] 和p[3]? (b)比較失配發生回溯時,接下來s[9] 會和p[2]比較,此時這個比較是多余的必定會再次失配?。把多余的比較剔除掉就是改進的next。
如果回溯的值與原值相等即,? P[j] = P[k]?時直接 另?P[j] = P[next[k]];
hpp:
// // Created by hu on 2020/8/5. //#ifndef SMARTDONGLIB_SSTRING_H #define SMARTDONGLIB_SSTRING_H #include <string> #include <vector>namespace SmartDongLib {class SString {public:SString(){ str_="";}explicit SString(std::string str): str_(std::move(str)){}SString(const SString& str){ str_=str.str_;}int length(){return str_.length();}SString copy(){return *this;};bool isEmpty(){return str_.empty();}void clear(){str_=""; }SString concat(const SString& str2){ SString ret(str_ + str2.str_); return ret;}SString subString(int pos, int len){SString ret (str_.substr(pos, len)); return ret;}SString subString(int pos=0){SString ret (str_.substr(pos)); return ret;}int index( const SString& , int pos =0);int index_KMP(SString& str2, int pos=0);SString replace(SString src, const SString& target);void strinsert(int pos,const SString& T){str_.insert(pos, T.str_);}void strdelete(int pos,int len){str_.erase(pos,len);}SString operator+(std::string str1){ return SString(str_ + std::move(str1));}SString operator+=(std::string str1){ return SString(str_ + std::move(str1));}bool operator==(const std::string& str1){return str_==str1;}std::string get(){return str_;}void getnext(int next[]);private:std::string str_;}; }#endif //SMARTDONGLIB_SSTRING_Hcpp? :
// // Created by hu on 2020/8/5. //#include "sdstructure/linearlist/SString.h" namespace SmartDongLib {inline int SString::index(const SString& str2, int pos ) {int iptr = pos , jptr = 0;while (iptr < str_.length() && jptr<str2.str_.length()){if (str_[iptr] == str2.str_[jptr]){//相等則比較下一位iptr++ ; jptr++;} else{//不相等則回溯,模式串指針從0 開始 i 回溯到原先的起始值+1 , 現值i'與原先的起始值i 滿足 i'-i=j'-j其中j=0iptr = iptr - jptr+1;jptr = 0;}}if (jptr >=str2.str_.length()){return iptr - jptr;}return -1;}/*** <p> 1 . 求next數組,有了next數組后一個一個匹配,如果失配讓 j = next[j];* @param substr* @param pos* @return*/inline int SString::index_KMP(SString& substr, int pos) {int i=pos, j=0;int next[substr.str_.length()];substr.getnext(next);int thisLen=length(),sublen=substr.length();while ( i < thisLen && j < sublen){if (j==-1 || str_[i] == substr.str_[j]){i++;j++;} else{j=next[j];}}if (j >= sublen){int ret =i-sublen;return ret;}return -1;}inline SString SString::replace(SString src, const SString& target) {if (src.str_.empty()){return *this;}int index=0;while ( index != -1) {index = index_KMP(src);if(index != -1) {str_.erase(index, src.str_.length());str_.insert(index, target.str_);}}return *this;}/*** <p>原理: 當求next的第j個元素時,看 j-1 個元素開始和第0個元素比對,k不斷增加取最大值滿足 0<k<j* 從后往前數k個即第 j-k+1...j-1元素與 0...k-1* 如 abaabcac 當(a)j=0 next[0]=0 ; (b)j =1 ,next[1]=1,;(a) j=2時,k=1,第1個元素和第0個元素比對即a和b比不對就是1* 當(a)j=3,k=1,第2個元素和第0個元素 比a和a匹配上了 那就是next[3]=2;* @param substr* @return*/inline void SString::getnext(int next[]) {const int len =str_.length(); // next.resize(len,-1);int i = 0,j=-1;next[0]=-1;while (i<len){if (j==-1 ||str_[i] == str_[j]){++i;++j;next[i]=j;} else{j=next[j];}}//return next;} }example:
// // Created by hu on 2020/8/6. //#include <iostream> #include "sdstructure/linearlist/SString.cpp" using namespace std; using namespace SmartDongLib; int main(){SString mainstr("acabaabaabcacaabc");SString substr("abaabcac");//-1 0 -1 1 0 2 -1 1SString target("ijn");int next[substr.length()];substr.getnext(next);for (int i :next){cout<<i<<" ";}int index=mainstr.index_KMP(substr);int index2=mainstr.index(substr);cout<<endl<<index;cout<<endl<<index2;mainstr.replace(substr,target);cout<<endl<<mainstr.get(); }總結
以上是生活随笔為你收集整理的C++ : KMP 字符串匹配算法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Oracle 原理: 物化视图,快照,实
- 下一篇: C++ :Signal: SIGSEGV