数据结构-串(第四章)的整理笔记,若有错误,欢迎指正。
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)的表。
下面用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 [ n e x t [ j ] ] 。相等则将
n
e
x
t
[
j
]
next[j]
n e x t [ j ] 修正为
n
e
x
t
[
n
e
x
t
[
j
]
]
next[next[j]]
n e x t [ n e x t [ 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 = 1 t [ 1 ] = ( t [ n e x t [ 1 ] ] → t [ 0 ] ) n e x t [ 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 = 2 t [ 2 ] = ( t [ n e x t [ 2 ] ] → t [ 0 ] ) n e x t [ 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 = 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
=
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 = 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
=
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 = 5 t [ 5 ] = ( t [ n e x t [ 5 ] ] → t [ 2 ] ) n e x t [ 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 = 6 t [ 6 ] = ( t [ n e x t [ 6 ] ] → t [ 1 ] ) n e x t [ 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 ;
}
运行结果