一、 KMP算法解決什麼問題?
KMP解決的是用線性複雜度在主串中查找第一次出現模式串的下標。
如果使用普通方法,那就是用二重循環搜索,時間複雜度爲 O(M*N)。M爲主串長度,N爲模式串長度。
【舉例子】
使用KMP算法,我們可以用 O(N) 的時間複雜度在主串"abcdef@abga@cd"中查找模式串 “abga”。
二、理解KMP算需要理解哪些部分?
- 前綴、後綴概念
- 數組
- 數組的迭代求法
- 根據數組移動指向模式串的指針匹配主串
三、 拆分講解
1. 前後綴概念、next[N]數組的意義
【舉例子】
字符串str="babdefbab"的不同長度的前綴、後綴如表1.。
長度 | 前綴 | 後綴 |
---|---|---|
1 | b | b |
2 | ba | ab |
3 | bab | bab |
4 | babd | fbab |
5 | babde | efbab |
6 | babdef | defbab |
7 | babdefb | bdefbab |
8 | babdefba | abdefbab |
9 | babdefbab | babcdefbab |
其中相同長度的前綴和後綴相等的有b、bab、babdefbab。
相同長度的真前綴和真後綴相等的有b、bab。其中長度最長的相等真前後綴爲bab。
【概念】
根據上面的例子我們可以抽象前綴、後綴的概念
- 前綴指字符串的任意首部。 真前綴指不包含本身字符串的任意首部,字符串的真前綴長度要小於字符串的長度。
- 後綴指字符串任意尾部。字符串的真後綴的長度要小於字符串的長度。
2. 數組的意義
【概念】
表示的下標爲 到 的子串的最長的相等的真前後綴的長度。的範圍爲[-1,N-1]$。
【舉例子】 仍然用字符串str="babdefbab"舉例
i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
next[i] | -1 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 2 |
3. 數組的迭代求法
- 先看代碼
void nextSolution(int *next,int len,char *pattern){
int index=0,k=-1;
next[0]=k;
while(index<len-1){
if(k==-1||pattern[index]==pattern[k]){//注意next[0]=-1的特例
next[++index]=++k;
}else{
k=next[k]; //調整k
}
}
}
- 看圖解釋
初始值
- 當時,next[i+1]=k+1。
- 當時,next[i+1]=k+1。
- 當時,迭代k=next[k]。
4. 根據數組移動指向模式串的指針匹配主串
- 先看代碼
int kmp(char * haystack, char * needle){
const int len_ned=strlen(needle);
if(len_ned==0)//模式串爲""默認返回0
return 0;
const int len_hay=strlen(haystack);
if(len_ned>len_hay)//模式串長度大於主串長度
return -1;
int next[len_ned];
nextSolution(next,len_ned,needle);
int index_hay=0,index_needle=0;
while(index_hay<len_hay&&index_needle<len_ned){
if(index_needle==-1||haystack[index_hay]==needle[index_needle]){//注意next[0]=-1的特例
index_hay++;
index_needle++;
}else{
index_needle=next[index_needle];
}
if(index_needle==len_ned){
return index_hay-len_ned;
}
return -1;
}
- 看圖講解