- kmp算法可以用作匹配字串问题的朴素算法的改进,相对与朴素的查找O(n^2)的时间复杂度,kmp算法只需要大致为O(n),大大提升了查找速度。
- kmp算法区别于朴素查找的算法的不同点就是它可以更高效的回溯比较。
-
上图第六个字母不同,朴素做法是从主串的第二个开始,重新遍历,而kmp算法更加高效。
-
它跳过了其中不可能的字符遍历,节省了时间,它只对子串进行了回溯,我们注意到不同的是‘=’和‘c’,主串仍然将从这个位置继续向后遍历。
-
这是直观上的比较
2. 最长相等前后缀:顾名思义就是一段长串中前后相同长度的部分是完全一样的,比如"aba",这一段最长相等前后缀就是a,它的长度为一,我们注意到上述图示中,第一次字符不匹配时的主串为“abccab”(我截取前一部分),它的最长相等前后缀为"ab",长度为2,我们注意到字串便是回溯到了这个位置。
所以我们要做的就是求出每一个字符的最长相等前后缀,并将他保留到数组,记作next数组,我们定义它表示next[i]这个位置前的最长相等前后缀长度,比如"ab",那么next[1]=0。
3.求next数组
此为图示:
char s[N]; int next[N]; void getnext(char s[]){ int i=0,j=-1,len=strlen(s); next[0]=-1;//第一个字符没有最长相等前后缀所以赋值-1 while(i<len-1){//注意到下方i++,所以i还是会到len-1 if(j==-1||s[i]==s[j]) i++,j++,next[i]=j;//保留最长相等前后缀 else j=next[j];//重新匹配最长相等前后缀 } }
4.KMP
char s[N],t[N];//t是子串 int kmp(char s[]){ getnext(s); int len1=strlen(s),i=0,j=0,d=0; int len2=strlen(t); while(i<len1&&j<len2){ if(j==-1||s[i]==t[j]) i++,j++;//相同继续匹配 else j=next[j];//不同回溯字串 } if(j==len2) reutrn i-len2;//返回下标 else return -1;//未找到 return d; }
5.不过kmp算法仍有可改进之处,那便是next数组,比如对于"aaabaaassssss","aaacbbb",当比较到第四个时,字串回溯为2,仍然是a,这就有待改进,我们可以优化next保存的值,尽可能跳过这种多余比较。
void getnext(char s[]){ int i=0,j=-1,len=strlen(s);
next[0]=-1; while(i<len-1){ if(j==-1||s[i]==s[j]){ i++,j++; if(s[i]!=s[j]) next[i]=j;//这等于原来的next[i]=j,代表着字串返回后相同,保存长度 else next[i]=next[j];//不同的的话就保存就再回溯一次的长度 }//为什么你不写一个直接保留最终位置的next呢?(我也不会,这还是看的别人代码。。。) else j=next[j]; } }
6.算法复杂度,假设主串长度n,字串为m,朴素做法最好情况为O(n),最坏为O(n*m);
kmp,假设主串长度n,字串为m,求next数组的时间复杂度为O(m),总时间复杂度为O(m+n)(我不会,不好意思,网上的太长了,也许以后会看看再补上)