题目
有主串S和子串T,找到主串S里和子串T相等的片段.
方法一.模式匹配算法
该方法从主串的第一个开始,与子串的第一个相比,如果相同则继续往下,直到成功结束,但在其过程中遇见不同时,将从主串的第二个开始,与子串的第一个相比,一直以此类推,直到成功找到.
该算法最坏的情况下是从主串第一个开始基本每一个都进行了对子串的全部遍历.假设主串有n个字符,子串有m个字符,则该算法的时间复杂度为O(m*n).
方法二.KMP算法及其优化
KMP算法相比前者就要快很多,时间复杂度为O(m+n),是一种典型的空间换时间的算法,但空间也仅仅是增加了m大小的整型数组空间,相比而言是非常划算的.
KMP算法用已经匹配好了的子串前面部分,来省去不必要的比较操作.
首先我们要明白最长相等前后缀
前缀指的是该串从前面开始依次往后数一个,两个,三个…等字符(不包括本身)的操作,例如abc的前缀就是a,ab.abcd的前缀就是a,ab,abc.而abc最长前缀指的就是ab了,abcd的最长前缀则是abc.
后缀指的是该串从后面开始依次往后数一个,两个,三个…等字符(不包括本身)的操作,但是这里的从后面数,指的是顺序,而并非倒序.例如abc的后缀就是c,bc.abcd的前缀就是d,cd,bcd.而abc最长后缀指的就是bc了,abcd的最长前缀则是bcd.
最长相等前后缀则是能让前后缀一样的最长长度了.例如abdaab,该串的最长相等前后缀就是2.
求最长相等前后缀是对子串进行的,将子串中每一个字符前面串的最长相等前后缀求出,放到next数组例如abcabd,其标号分别为0,1,2,3,4,5.
0号位置前没有元素自然称不上有最长相等前后缀,故记为-1,这里便于以后操作
1号位置前面只有一个元素,因为最长相等前后缀不包括本身,所以记为0;
2号位置前面有两个元素,可以看出最长相等前后缀为0,记为0.
同理3号位置记为0,4号位置记为1.5号位置记为2.
则该数组为
next[0] | next[1] | next[2] | next[3] | next[4] | next[5] |
---|---|---|---|---|---|
-1 | 0 | 0 | 0 | 1 | 2 |
这是准备工作完成了,先在我们先来理解一下这个算法的思想
a | b | c | a | b | e | a | |||
---|---|---|---|---|---|---|---|---|---|
a | b | c | a | b | d |
a | b | c | a | b | e | a | |||
---|---|---|---|---|---|---|---|---|---|
a | b | c | a | b | d |
位置从0开始算起
从图中我们可以看出是在子串第5个位置开始不一样的,那我们是不是应该重新从主串的第1个,子串的第0个比较呢,其实并不用,因为我们已经记录了第5个以前的最长相等前后缀了可以直接右滑到主串的第3个位置就好.因为最长前后缀,可以让我们在不匹配字符前面,用后缀和前缀对上去,这样就快上很多,而其神奇之处是,当发生不匹配时,则可以直接从主串的5位置和next[5]位置再次进行匹配.假设i为主串字符的位置,j为子串字符的位置.则i=5,j=5时发生不匹配,直接从子串j=next[j]位置再与其主串i位置进行匹配.
其原因就是我们已经知道不匹配元素的前面串的最大前后缀长度,直接让已经匹配过的主串和子串,尾对首就好.
下面来看如何求得next数组,先看代码
typedef struct
{
char date[MAXSIZE];
int length;
}String;
void GetNext(String t, int next[])
{
int j, k;
j=0;
k=-1;
next[0] = -1;
while(j<t.length-1)
{
if(k==-1||t.date[j]==t.data[k])
{
j++;
k++;
next[j] = k;
}else
{
k = next[k];
}
}
}
先设立两个变量j,k, j记为0,k记为-1,这样方便计算.
还是举例abcabde.
j | j | j | j | j | j | j | j | j | |||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 2 | 3 | 4 | 5 | 6 | |||||||
k | k | k | k | k | k | k | |||||||
-1 | 0 | -1 | 0 | -1 | 0 | 1 | 2 | 0 | -1 | 0 | |||
next[0] | next[1] | next[2] | next[3] | next[4] | next[5] | next[6] | |||||||
-1 | 0 | 0 | 0 | 1 | 2 | 0 |
这是他们值的变化,if语句里面的好理解,即是遇见相同的j,k+1,然后让数组对应的值等于k;
而不容易理解的是esle里的k=next[k],其原因和子串与主串匹配的道理一样,我们让next数组里记录的是k以前的最大前后缀长度,当不匹配时,自然要让k回溯到那个位置,这样j前面的一段就能和回溯到的k位置之前串对应上.看图片
这就是next数组的求法,但具体的还要自己一步一步来体会
KMP算法
int KMP(String s,String t)
{
int next[MaxSize],i=0,j=0;
GetNext(t,next);
while (i<s.length && j<t.length)
{
if (j==-1 || s.data[i]==t.data[j])
{
i++;j++;
}
else j=next[j];
}
if (j>=t.length)
return(i-t.length);
else
return(-1);
}
优化后的是假如子串回溯的位置和原字母相同,那该位置与主串对应位置肯定仍旧不同,还需要继续回溯,为减少回溯,我们可以让相同位置的数组的值相同,即第二个出现的相同字母和第一个相等.
这是优化后的KMP算法
void GetNextval(String t,int nextval[])
{
int j=0,k=-1;
nextval[0]=-1;
while (j<t.length)
{
if (k==-1 || t.data[j]==t.data[k])
{
j++;k++;
if (t.data[j]!=t.data[k])
nextval[j]=k;
else
nextval[j]=nextval[k];
}
else k=nextval[k];
}
}
希望可以带给大家一些帮助.