数据结构-串(第四章)的整理笔记,若有错误,欢迎指正。
Brute—Force算法
- Brute—Force(暴力)简称为BF算法,也称简单匹配算法,采用穷举方法。其思想就是将目标串S的第一个字符与模式串T的第一个字符进行匹配,若相等,则继续比较S的第二个字符和T的第二个字符;若不相等,则比较S的第二个字符和T的第一个字符,依次比较下去,直到得出最后的匹配结果。BF算法是一种蛮力算法。
- 时间复杂度:
最坏情况下要进行(n-m+1)m次比较,时间复杂度为O(m×n)。
最好情况下的时间复杂度为O(m),即主串的前m个字符正好等于模式串的m个字符。
平均时间复杂度O(m×n)
- 其中,n和m分别为主串和模式串的长度
算法实现
int Index(SqString s, SqString t)
{
int i = 0, j = 0;
while (i < s.length && j < t.length)
{
if (s.data[i] == t.data[j])
{
i++; j++;
}
else
{
i = i - j + 1;
j = 0;
}
}
if (j >= t.length) return i - t.length;
else return -1;
}
分析
- 运行结果:


KMP算法
- KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。其核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。
求KMP算法中next数组的两种方法
方法一
- next数组的前两个空位每次都可以无脑写0和1(如果写成-1和0,则数组中的值全部也要减1)
- 具体求法看下图:

最终求得串"google"的next数值为
j |
1 |
2 |
3 |
4 |
5 |
6 |
t[j] |
g |
o |
o |
g |
l |
e |
next[j] |
0 |
1 |
1 |
1 |
2 |
1 |
或者也可以写为
j |
0 |
1 |
2 |
3 |
4 |
5 |
t[j] |
g |
o |
o |
g |
l |
e |
next[j] |
-1 |
0 |
0 |
0 |
1 |
0 |
方法二
- 前缀指除最后一个字符以外,字符串的所有头部子串;
- 后缀指除第一个字符外,字符串的所有尾部子串;
- 部分匹配值则为字符串的前缀和后缀的最长相等前后缀长度。
- 'a’的前缀和后缀都为空集,最长相等前后级长度为0。
- 'ab’的前缀为{a},后缀为{b},{a}∩{b}= ∅ \emptyset ∅,最长相等前后缀长度为0。
- 'aba’的前缀为{a,ab},后缀为{a,ba},{a,ab}∩{a,ba}={a},最长相等前后缀长度为1。
- 'abab"的前缀{a,ab,aba},后缀{b,ab,bab},{a,ab,aba}∩{b,ab,bab}={ab},最长相等前后缀长度为2。
- 'ababa’的前缀{a,ab,aba,abab},后缀{a,ba,aba,baba},{a,ab,aba,abab}∩{a,ba,aba,baba}={a,aba},最长相等前后缀长度为3。
- 故字符串’ ababa’的部分匹配值为00123。
- 将部分匹配值写成数组形式,就得到了部分匹配值(Partial Match,PM)的表。
编号 |
1 |
2 |
3 |
4 |
5 |
S |
a |
b |
a |
b |
a |
PM |
0 |
0 |
1 |
2 |
3 |
- 下面用PM表来进行字符串匹配:(主串为:ababcababab,子串为ababa)
主串 |
a |
b |
a |
b |
c |
a |
b |
a |
b |
a |
b |
子串 |
a |
b |
a |
b |
a |
|
|
|
|
|
|
第一趟 |
a |
b |
a |
b |
a |
|
|
|
|
|
|
- 第一趟匹配过程:发现a与c不匹配,前面的4个字符’abab’是匹配的,查表可知,最后一个匹配字符’b’对应的部分匹配值为2,因此按照公式:移动位数=已匹配的字符数-对应的部分匹配值,算出子串需要向后移动4-2=2位。
主串 |
a |
b |
a |
b |
c |
a |
b |
a |
b |
a |
b |
子串 |
|
|
a |
b |
a |
b |
a |
|
|
|
|
第二趟 |
|
|
a |
b |
a |
b |
a |
|
|
|
|
- 第二趟匹配过程:发现a与c不匹配,前面2个字符’ab’是匹配的,最后一个匹配字符’b’对应的部分匹配值为0,算出子串需要向后移动2-0=2位。
主串 |
a |
b |
a |
b |
c |
a |
b |
a |
b |
a |
b |
子串 |
|
|
|
|
a |
b |
a |
b |
a |
|
|
第三趟 |
|
|
|
|
a |
b |
a |
b |
a |
|
|
- 第三趟匹配过程:发现a与c不匹配,此时第一个字符就不匹配,子串向后移动1位。
主串 |
a |
b |
a |
b |
c |
a |
b |
a |
b |
a |
b |
子串 |
|
|
|
|
|
a |
b |
a |
b |
a |
|
第四趟 |
|
|
|
|
|
a |
b |
a |
b |
a |
|
- 子串全部比较完成,匹配成功。整个匹配过程中,主串始终没有回退,故KMP算法可以在O(n+m)的时间数量级上完成串的模式匹配操作,大大提高了匹配效率。
算法实现
void Get_Next(SqString t, int next[])
{
int i = 0, j = -1;
next[0] = -1;
while (i < t.length - 1)
{
if (j == -1 || t.data[i] == t.data[j])
{
i++; j++;
next[i] = j;
}
else j = next[j];
}
}
int Index(SqString s, SqString t)
{
int next[MaxSize], i = 0, j = 0;
Get_Next(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算法
- 从j=1开始,依次判断 t [ j ] t[j] t[j]是否等于 t [ n e x t [ j ] ] t[{next[j]}] t[next[j]]。相等则将 n e x t [ j ] next[j] next[j]修正为 n e x t [ n e x t [ j ] ] next[next[j]] next[next[j]],直至两者不相等为止。
- j = 1 t [ 1 ] ≠ ( t [ n e x t [ 1 ] ] → t [ 0 ] ) n e x t [ 1 ] = 0 j=1\;\;t[1]≠(t[next[1]]\rightarrow t[0])\;\;next[1]=0 j=1t[1]=(t[next[1]]→t[0])next[1]=0
- j = 2 t [ 2 ] ≠ ( t [ n e x t [ 2 ] ] → t [ 0 ] ) n e x t [ 2 ] = 0 j=2\;\;t[2]≠(t[next[2]]\rightarrow t[0])\;\;next[2]=0 j=2t[2]=(t[next[2]]→t[0])next[2]=0
- j = 3 t [ 3 ] = ( t [ n e x t [ 3 ] ] → t [ 0 ] ) n e x t [ 3 ] = ( n e x t [ n e x t [ 3 ] ] → n e x t [ 0 ] ) = − 1 j=3\;\;t[3]=(t[next[3]]\rightarrow t[0])\;\;next[3]=(next[next[3]]\rightarrow next[0])=-1 j=3t[3]=(t[next[3]]→t[0])next[3]=(next[next[3]]→next[0])=−1
- j = 4 t [ 4 ] = ( t [ n e x t [ 4 ] ] → t [ 1 ] ) n e x t [ 4 ] = ( n e x t [ n e x t [ 4 ] ] → n e x t [ 0 ] ) = 0 j=4\;\;t[4]=(t[next[4]]\rightarrow t[1])\;\;next[4]=(next[next[4]]\rightarrow next[0])=0 j=4t[4]=(t[next[4]]→t[1])next[4]=(next[next[4]]→next[0])=0
- j = 5 t [ 5 ] ≠ ( t [ n e x t [ 5 ] ] → t [ 2 ] ) n e x t [ 5 ] = 2 j=5\;\;t[5]≠(t[next[5]]\rightarrow t[2])\;\;next[5]=2 j=5t[5]=(t[next[5]]→t[2])next[5]=2
- j = 6 t [ 6 ] ≠ ( t [ n e x t [ 6 ] ] → t [ 1 ] ) n e x t [ 6 ] = 1 j=6\;\;t[6]≠(t[next[6]]\rightarrow t[1])\;\;next[6]=1 j=6t[6]=(t[next[6]]→t[1])next[6]=1
对于每一个串,next数组的next[0]=-1,next[1]=0;nextval数组的nextval[0]=-1,nextval[1]=0,这些值都是固定的!
j |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
t[j] |
a |
b |
c |
a |
b |
a |
a |
next[j] |
-1 |
0 |
0 |
0 |
1 |
2 |
1 |
nextval[j] |
-1 |
0 |
0 |
-1 |
0 |
2 |
1 |
算法实现
void Get_Nextval(SqString t, int nextval[])
{
int i = 0, j = -1;
nextval[0] = -1;
while (i < t.length)
{
if (j == -1 || t.data[i] == t.data[j])
{
i++; j++;
if (t.data[i] != t.data[j])
nextval[i] = j;
else nextval[i] = nextval[j];
}
else j = nextval[j];
}
}
int Index(SqString s, SqString t)
{
int nextval[MaxSize], i = 0, j = 0;
Get_Nextval(t, nextval);
while (i < s.length && j < t.length)
{
if (j == -1 ||s.data[i] == t.data[j])
{
i++; j++;
}
else
j = nextval[j];
}
if (j >= t.length) return i - t.length;
else return -1;
}
Brute—Force算法和KMP算法的比较
- Brute—Force算法时间复杂度:O(m×n);KMP算法时间复杂度:O(m+n)。
- Brute—Force算法简单且易于理解,但效率不高,主要原因是主串指针在某些情况下需回溯;而KMP算法在此基础之上有较大的改进,主要是消除了主串指针的回溯,从而使算法效率有了某种程度的提高。
- !注意:KMP算法的平均时间复杂度虽然优于BF算法,但并不等于说任何情况下KMP算法都优于BF算法。当模式串的next数组中next[0]=-1,而其他元素值均为0时,KMP算法退化为BF算法。
完整代码
#include<stdio.h>
#define MaxSize 50
typedef char ElemType;
typedef struct
{
ElemType data[MaxSize];
int length;
}SqString;
void StrAssign(SqString& s, char cstr[])
{
int i;
for (i = 0; cstr[i] != '\0'; i++)
s.data[i] = cstr[i];
s.length = i;
}
void PrintString(SqString s)
{
for (int i = 0; i < s.length; i++)
printf("%c", s.data[i]);
}
int Index(SqString s, SqString t)
{
int i = 0, j = 0;
while (i < s.length && j < t.length)
{
if (s.data[i] == t.data[j])
{
i++; j++;
}
else
{
i = i - j + 1;
j = 0;
}
}
if (j >= t.length) return i - t.length;
else return -1;
}
void Get_Next(SqString t, int next[])
{
int i = 0, j = -1;
next[0] = -1;
while (i < t.length - 1)
{
if (j == -1 || t.data[i] == t.data[j])
{
i++; j++;
next[i] = j;
}
else j = next[j];
}
}
int KMPIndex(SqString s, SqString t)
{
int next[MaxSize], i = 0, j = 0;
Get_Next(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;
}
void Get_Nextval(SqString t, int nextval[])
{
int i = 0, j = -1;
nextval[0] = -1;
while (i < t.length)
{
if (j == -1 || t.data[i] == t.data[j])
{
i++; j++;
if (t.data[i] != t.data[j])
nextval[i] = j;
else nextval[i] = nextval[j];
}
else j = nextval[j];
}
}
int KMPIndex1(SqString s, SqString t)
{
int nextval[MaxSize], i = 0, j = 0;
Get_Nextval(t, nextval);
while (i < s.length && j < t.length)
{
if (j == -1 || s.data[i] == t.data[j])
{
i++; j++;
}
else
j = nextval[j];
}
if (j >= t.length) return i - t.length;
else return -1;
}
int main()
{
SqString s, t;
int j;
int next[MaxSize], nextval[MaxSize];
ElemType cstr1[] = "abcabcdabcdeabcdefabcdefg";
StrAssign(s, cstr1);
printf("s:");
PrintString(s);
ElemType cstr2[] = "abcdeabcdefab";
StrAssign(t, cstr2);
printf("\nt:");
PrintString(t);
printf("\n简单匹配算法:\n");
printf(" t在s中的位置=%d\n", Index(s, t));
Get_Next(t, next);
Get_Nextval(t, nextval);
printf(" j ");
for (j = 0; j < t.length; j++) printf("%4d", j);
printf("\n");
printf(" t[j] ");
for (j = 0; j < t.length; j++) printf("%4c", t.data[j]);
printf("\n");
printf(" next ");
for (j = 0; j < t.length; j++) printf("%4d", next[j]);
printf("\n");
printf("nextval ");
for (j = 0; j < t.length; j++) printf("%4d", nextval[j]);
printf("\n");
printf("KMP算法:\n");
printf(" t在s中的位置=%d\n", KMPIndex(s, t));
printf("改进的KMP算法:\n");
printf(" t在s中的位置=%d\n", KMPIndex1(s, t));
return 0;
}
运行结果

所有评论(0)