求一个字符串T的子串S的位置一般算法是
int len=T.len-S.len,j,i;
for(i=0;i<len;i++)
{
for(j=0;j<S.len;j++)
if(T[i+j]!=S[j]) break;
if(j==S.len) break;
}
这种算法写起来很简单,复杂度最大为O(T.len*S.len)
例如T[]="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaAAaaaaaaaaaa";
而S[]="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaAA" ;
这样的数据会使内嵌的for()每次都循环S.len() 次,如果这两个串又是特别长的话,程序效率将会特别低下;
KMP算法就出来了的。
讲这个算法之前,先说一下next[ ]数组
这个数组的每一个元素,都对应着S串中的每一个字符
S = {a,c,a,d,c,a,d}
next.={0,1,1,2,1,1,2}
. S={a,b,c,c,a,b,c}
next={0,1,1,1,1,2,3}
next[i]中的值,即为S[i]之前的序列能和S从开头算起的最大重合的个数+1;
比如 :
0 1 2 3 4 5 6
S={a,b,c,c,a,b,c}
next={0,1,1,1,1,2,3}
其中next[6]=3,是因为S[6]前面有S[4]=a;
S[5]=b;
而S[0]=a;
S[1]=b;
重合,所以next[6]记为3;
为什么不记为4?
因为S[0]=a;
S[1]=b;
S[2]=c;
而S[3]=c;
S[4]=a;
S[5]=b;
显然不符合.....
这个数组的每一个元素,都对应着S串中的每一个字符
S = {a,c,a,d,c,a,d}
next.={0,1,1,2,1,1,2}
. S={a,b,c,c,a,b,c}
next={0,1,1,1,1,2,3}
next[i]中的值,即为S[i]之前的序列能和S从开头算起的最大重合的个数+1;
比如 :
0 1 2 3 4 5 6
S={a,b,c,c,a,b,c}
next={0,1,1,1,1,2,3}
其中next[6]=3,是因为S[6]前面有S[4]=a;
S[5]=b;
而S[0]=a;
S[1]=b;
重合,所以next[6]记为3;
为什么不记为4?
因为S[0]=a;
S[1]=b;
S[2]=c;
而S[3]=c;
S[4]=a;
S[5]=b;
显然不符合.....
总而言之,next【i】的值就是 S[i]中,若 i 左移k位,得到得S[i-k]~S[i]这个子串与S[0]~S[k]相同,那么next[i]的值即为所能取到的最大的k再加上1;
注:next[]的值,以及元素的个数都只与S串有关,它是描述S串属性的量(S串是子串)
next[]数组的作用是:当确定一个首地址i,接着向后寻找时,找了m次都成功S与T都成功匹配上了(字符相同),但是,找到m+1位,发现S与T不一样了,之前那种一般算法就是i向下挪一位,接着j=0重新开始查找,但是我如果有next[]数组我就可以很轻易的得知m+1位的前next[m+1]-1位,一定是和T匹配上的,然后j直接向后挪动next[m+1]位比较,直到next[]=1;(事实上根本用不了几次)然后i直接就可以向下 挪一位;
注:next[]的值,以及元素的个数都只与S串有关,它是描述S串属性的量(S串是子串)
next[]数组的作用是:当确定一个首地址i,接着向后寻找时,找了m次都成功S与T都成功匹配上了(字符相同),但是,找到m+1位,发现S与T不一样了,之前那种一般算法就是i向下挪一位,接着j=0重新开始查找,但是我如果有next[]数组我就可以很轻易的得知m+1位的前next[m+1]-1位,一定是和T匹配上的,然后j直接向后挪动next[m+1]位比较,直到next[]=1;(事实上根本用不了几次)然后i直接就可以向下 挪一位;
int i=0,j=-1;
next[0]=-1;
while(i<s.length())
{
if(j==-1||s[i]==s[j])
{
i++;
j++;
next[i]=j;
}
else j=next[j];
}
//求next的代码
int find(const string& t,const string& s)
{
int i=0,j=0;//可以通过改变i的值,查找第i位后的s串
while(i<(int)t.length()&&j<(int)s.length())//string.length()返回值是无符号整型
{
if(j==-1||t[i]==s[j])
{
i++;
j++;
}
else j=next[j];
}
if(j==t.length()) return i-t.length();
else return -1;
}
next[]数组的改进求法
2016年追加:
我不知道当时为什么那么奇葩的把next[]数组构造成那样,不过既然思想没有错,那我就保留当时的我的看法,不改前面了,今天回过头来看了一下,发现对next[]数组的作用描述的不够清晰,我再重新描述一下吧
int i=0,j=-1;
next[0]=-1;
while(i<s.length())
{
if(j==-1||s[i]==s[j])
{
i++;
j++;
if(s[i]!=s[j])
next[i]=j;
else next[i]=next[j];
}
else j=next[j];
}
我不知道当时为什么那么奇葩的把next[]数组构造成那样,不过既然思想没有错,那我就保留当时的我的看法,不改前面了,今天回过头来看了一下,发现对next[]数组的作用描述的不够清晰,我再重新描述一下吧
字符串为T,子串为S,当比较到T[i]与S[j]时,发现T[i]与S[j]不相等,首先这里要明确一个问题,此时T[i-j]==S[0],T[i-j+1]==S[1],T[i-j+2]==S[2]....
T[i-1]==S[j-1];意思就是,S[j]之前的与T[i]之前的完全相等。所以就牵扯到一个重复的问题,比如S=="abcabdc"如果现在j=5时,发现不等,那么T[i-2]=='a',T[i-1]=='b',我只需再比较T[i]==S[2]。说白了,next[]串记录的就是,如果比较到这一位,发现不等,那么,T[i]要重新和S[i]的第几位比较。当然如果S[0]的位置出现不相等了,那么i就必须向下挪一位了。,所以不妨把这种情况标记为-1
刚刚说
S=="abcabdc"很容易得到next[]="-1,0,0,0,1,2,0",代码上面已经给出了,虽然跟我现在说的有点不一样,稍微做些改变就可以了。
2018/04/08追加:
距离写下这篇博客(2014年)已经过去4年了,无意中又翻到了这篇博客,2014年写的东西太乱,况且只是生搬教材的东西,不做评价。只评价一下2016年写的追加内容。
我在2016年追加写到“ S=="abcabdc"很容易得到next[]="-1,0,0,0,1,2,0" ”。这个说法是有点问题的,首先再明确一下next数组是什么。next数组中的第i个值代表:当前字符与s[i]对比失败,要和s[next[i]] 继续对比,next存的是比较失败后的合格前缀,next[x] = -1代表没有合适前缀,s[i]需要后移。再看next数组,明显是有问题的,比如next[3]:s[3]就等于s[0],如果比较失败后,没必要再和s[0]比,多比较了一次。比如next[4]:s[4]比较失败,应该直接和s[0]比较,也多比较了一次。next数组十分关键,决定了匹配效率。例子中冗余比较只有一个维度不多,但是在串比较长的情况下,可能出现多度冗余(next[i]无效比较多次,才进行有效的比较)影响效率。
正确的next[] = "-1,0,0,-1,0,2,0"
贴一下next[]的生成代码:
vector<int> getKmpNext(string &s){
vector<int> next(s.sie(),-1);
for(int i=1,j=0;i<s.size();i++){
if(s[i] == s[j]){
next[i] = next[j];
j++;
}else{
next[i] = j;
while(j != -1 && s[i] != s[j])
j = next[j];
j++;
}
}
}