KMP算法,字符串的模式匹配。
简单的匹配也是可以的,而且很容易就想到,只是在时间上浪费了很多不必要的比较。
那么在真正匹配之前对目标子串做一个处理,将会在一定程度上省去很多不必要的比较,大大提高效率。
比如,target:ababaaaba 这样一个字符串在str字符串中去寻找匹配。那么首先对当前target字符串做一个处理,所谓处理就是记录一下那些字符是与前面重复了,再匹配过程中已经想等就不用再重复比较,通常为target处理得到一个next数组,这个数字就是记录到当前有多少个字符与重复相等的。具体:
next【i】:
0, i=0或者没有前缀后缀字符相等;
max{k|0<=k<=j,p0...pk=pj-k...pj}集合非空,即有前后缀相等;
例子:ababaaaba
next: next【0】=0;
next【1】=0(a!=b);
next【2】=1(aba中a==a);
next【3】=2(abab中ab=ab);
next【4】=3(ababa中aba=aba);
next【5】=1(ababaa中abab != abaa, j回溯往前为next[2]=1, a[1]!=a[5], 则j继续回溯为next[0]=0,a[0]==a[5]);
next【6】=1(j=1,但a[1]!=a[6],所以j回溯为a[0]=0,a[0]==a[6]);
next【7】=2(a[1]==a[7]);
next【8】=3(a[2]==a[8]);
获取next数组的函数:
vector<int> getNext(const string &needle) { vector<int> next(needle.size()); if (needle.size() == 0) return next; next[0] = 0; int i = 1, j = 0; while (i<needle.size()) { if ( needle[i] == needle[j]) //前面最后一个字符等于后面最后一个字符,所以next数组当前标记为j+1 next[i++] = ++j; else if (j != 0) j = next[j - 1];//不等的时候,j往前回溯一个,跳到上一个比较下来相等的位置下标 else next[i++] = 0; //前后的后缀字符不等,且此时j为0,即与第一个字符比较既然不等,next数据当然将其标记为0 } return next; }
得到next数组之后,在匹配中用两个指针,i表示str中下标位置,j表示target中下标位置。每次逐一比较,如果相等依次往后推进,如果不等,则让j回溯到上一next【j】,即中间重复的就不再比较辣,下次开始从回溯的位置开始比较。
int find(const string &haystack, const string &needle, int pos) { if (haystack.size()<needle.size() || pos<0 || pos>haystack.size() - 1) return -1; vector<int> next = getNext(needle); int i = pos, j = 0; while (i<haystack.size() && j<needle.size()) { if (haystack[i] == needle[j]) //如果两个字符串的当前字符相等,则一次往后推一个就好 { ++i; ++j; } if (j == needle.size()) //如果,目标字符已经达到最末尾,则可以返回超找到的位置了。 return i - j; if (i<haystack.size() && haystack[i] != needle[j]) //如果当前比较的字符不相等,则按照next数组中往上回溯一个下标,再次比较即可 { if (j != 0)//简单的j按照next数组回溯 j = next[j - 1]; else //目标字符串的第一个字符就没有匹配上,则让长的字符数组往后推进一个再进行比较 ++i; } } return -1; }
以上代码为从str中的pos位置开始往后查找匹配。
next 数组改进:每次对于是与第一个字符比较还是中间的字符比较区分开来,即记录上中间已经相等的字符,匹配的时候可以直接略过。
如:aaaabcdef中匹配aaaaax
现在当匹配到第四个d与a不等时,不用再去比较b和a前面的a,直接跳到第一个a,因为中间的a是相等的,在nextval数组中已经记录略过了
vector<int> GetNextVal(const string &str) { vector<int> nextVal(str.size()); if (str.size() == 0) return nextVal; int i = 0,j = 0; while (i<str.size()-1) { if (j == 0) nextVal[++i] = j++; else if (str[i] == str[j - 1]) nextVal[++i] = nextVal[j++]; else j = nextVal[j - 1]; } return nextVal; }