朴素匹配
朴素匹配又称暴力匹配,其时间复杂度为O(m*n)
例如:在ABEECDEEF中查找是否有子串EEF
1.
ABEECDEEF
EEF
将A于E匹配发现不相同,子串回到起点,主串后移一位
2.
ABEECDEEF
EEF
B与E匹配发现不相同,子串回到起点,主串后移一位
3.
ABEECDEEF
EEF
E与E匹配成功,子串和主串都后移一位,继续匹配
4.
ABEECDEEF
EEF
此时,E与E匹配相同,子串和主串都后移一位,继续匹配
5.
ABEECDEEF
EEF
此时,F与C匹配不相同 ,子串回到起点,主串回到第一个匹配上的字符的下一个位置(回到第二个E)
6.
ABEECDEEF
EEF
继续匹配,直至遇到第一个匹配上的字符
......
7.
ABEECDEEF
EEF
E与E匹配相同,子串和主串都后移一位,继续匹配
8.
ABEECDEEF
EEF
E与E匹配相同,子串和主串都后移一位,继续匹配
9.
ABEECDEEF
EEF
F与F匹配相同,此时子串已匹配完,匹配完成
代码如下:
#include
#include
#include
int BF(const char *str,const char *sub,int pos)
{
assert(str != NULL && sub != NULL);
int i = pos;
int j = 0;
int lens = strlen(str);
int lensub = strlen(sub);
while(j < lensub && i < lens)
{
if(str[i] == sub[j])
{
i++;
j++;
}
else
{
i = i-j+1;
j = 0;
}
}
if(j >= lensub)
{
return i-j;
}
else
{
return -1;
}
}
KMP算法
KMP算法是对朴素匹配的一个优化,在于匹配不成功时,子串有时可以不用回到起点,主串不用回到之前的某个位置
其时间复杂度为O(m+n),过程如下:
1.
BBC ABCDAB ABCDABCDABDE
ABCDABD
B与A不匹配,所以主串后移一位。
2.
BBC ABCDAB ABCDABCDABDE
ABCDABD
因为B与A不匹配,主串再往后移。
3.
BBC ABCDAB ABCDABCDABDE
ABCDABD
就这样,直到主串有一个字符,与子串的第一个字符相同为止。
4.
BBC ABCDAB ABCDABCDABDE
ABCDABD
接着比较子串和主串的下一个字符,还是相同。
5.
BBC ABCDAB ABCDABCDABDE
ABCDABD
直到主串有一个字符,与子串对应的字符不相同。(此时,空格与D不匹配时,你其实知道前面六个字符是"ABCDAB"。设法利用这个已知信息,不要把"搜索位置"移回已经比较过的位置,继续把它向后移,这样就提高了效率。)
空格与D不匹配时,前面六个字符"ABCDAB"是匹配的。最后一个匹配字符B对应的"部分匹配值"为2,因此按照下面的公式算出向后移动的位数:
移动位数 = 已匹配的字符数 - 对应的部分匹配值
所以,将子串回退4位,继续匹配
6.
BBC ABCDAB ABCDABCDABDE
ABCDABD
空格与C不匹配,这时,已匹配的字符数为2("AB"),对应的"部分匹配值"为0。
所以,移动位数 = 2 - 0,结果为 2,于是将子串回退2位
7.
BBC ABCDAB ABCDABCDABD
ABCDABD
空格与A不匹配,主串后移一位
8.
BBC ABCDAB ABCDABCDABDE
ABCDABD
逐位比较,直到发现C与D不匹配。于是,移动位数 = 6 - 2,将子串回退4位。
9.
BBC ABCDAB ABCDABCDABDE
ABCDABD
逐位匹配
10.
BBC ABCDAB ABCDABCDABDE
ABCDABD
直到搜索词的最后一位,发现完全匹配,于是匹配完成。
代码如下:
int Kmp(const char *str,const char *sub,int pos)
{
int i = pos;
int j = 0;
int lens = strlen(str);
int lensub = strlen(sub);
int *next = (int *)malloc(sizeof(int) * lensub);
assert(next != NULL);
GetNext(next,sub);
while(j < lensub && i < lens)
{
if(j == -1 || str[i] == sub[j])
{
i++;
j++;
}
else
{
j = next[j];
}
}
if(j >= lensub)
{
return i-j;
}
else
{
return -1;
}
}
next数组(部分匹配值)
next数组就是一个模式串的前缀和后缀的最大公共串长度
"前缀"指除了最后一个字符以外,一个字符串的全部头部组合;
"后缀"指除了第一个字符以外,一个字符串的全部尾部组合。
拿上述KMP算法的例子来说,求子串“ABCDABD”的next数组过程如下:
1.
A B C D A B D
-1 0
默认next[0]=-1,next[1]=0
2.
A B C D A B D
-1 0 0
"AB"的前缀为[A],后缀为[B],共有元素的长度为0;
3.
A B C D A B D
-1 0 0 0
"ABC"的前缀为[A, AB],后缀为[BC, C],共有元素的长度0;
4.
A B C D A B D
-1 0 0 0 0
"ABCD"的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为0;
5.
A B C D A B D
-1 0 0 0 0 1
"ABCDA"的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],共有元素为"A",长度为1;
6.
A B C D A B D
-1 0 0 0 0 1 2
"ABCDAB"的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为"AB",长度为2;
代码如下:
void GetNext(int *next,const char *sub)
{
next[0] = -1;
next[1] = 0;
int lensub = strlen(sub);
int i = 2;//当前的i
int k = 0;//前一项的K值
while(i < lensub)
{
if(k == -1 || sub[i-1] == sub[k])
{
next[i] = k+1;
i++;
k = k+1;
}
else
{
k = next[k];
}
}
}
nextval
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|---|
模式串(P) | A | B | C | D | A | B | D |
next | -1 | 0 | 0 | 0 | 0 | 1 | 2 |
nextval | -1 | 0 | 0 | 0 | -1 | 0 | 2 |
nextval数组的值怎么来的呢??????????
默认nextval[0]=-1
从1号下标开始比较,B和next[0]下标对应的A是否相等 (竖着看 竖着看!!同一列 )
相等时 nextval[1]=A的nextval值
不相等时 nextval[1]=B的next值
从2号下标开始比较,C和next[0]下标对应的A是否相等
相等时 nextval[1]=A的nextval值
不相等时 nextval[1]=C的next值
以此类推.....
代码如下:
void GetNextval(int *next,const char *sub)
{
nextval[0] = -1;
int lensub = strlen(sub);
int i = 1;//当前的i
int k = -1;//前一项的K值
while(i < lensub)
{
if(k == -1 || sub[i-1] == sub[k])
{
i++;
k = k+1;
if (nextval[i] != p[j])
{
nextval[i] = k;
}
else
{
nextval[i] = nextval[k];
}
}
else
{
k = nextval[k];
}
}
}
KMP算法 C语言 next数组