一、BF(brute force)算法
#include<stdio.h>
void main()
{
char s[]="abcdabcdaaabbbccc";
char t[]="aaabbb";//设定s为主串,t为模式(子串)
int i=0,j=0;
while(s[i]&&t[j])//当s,t中任一一个字符串空时结束循环
if(s[i]==t[j])//当前项s[i],t[j]匹配成功时,自动匹配下一个
{
i++;
j++;
}
else
{
i=i-j+1;
j=0;//匹配失败时,i进行回溯,i回到前一次匹配开头的下一个位置,j回到模式开头
}
if(!t[j]) printf("%d\n",i-j);
else printf("匹配失败!\n");
}
暴力算法,对模式和主串进行一个个的匹配。该算法的优点在于:算法简单易懂,能够通过代码就明白算法的意义;但同时也存在缺点,缺点是对已知信息的利用不够充分且时间复杂度不稳定(最高可达O(m*n),最低为O(n),其中m为主串长度,n为模式长度)。对信息的利用不够充分体现在,当我通过扫描模式后,对模式中的字符是已知的,但是当我匹配失败的时候,我每次还是从模式的开头重新进行扫描。这样,就浪费了我对于模式的已知信息。
例如:
我们有主串:abcdabcdbccbbbccc;有模式:bcdbca。假设我们的匹配已经进行到了如下的步骤:
此时s[10]!=t[5],按照上面的BF算法,我们就应该让i回溯(i=i-j+1=6),让j=0重新开始匹配。但是我们已经读取了模式t中的字符,即我们知道蓝色的“bc”和黄色的“bc”是同时存在于模式t中的,故我们只需要保持i=10不动,让模式t中的t[2]与主串中的s[10]开始比较,这样我们就节省了两次移动,五次比较,使得程序的效率大大提升。
那么现在的问题就是,我如何知道当s[i]!=t[j]时,我的模式串的哪一位同s[i]进行下一轮比较,这就引出了另一个串的模式匹配算法:KMP算法
二、KMP算法
next[]数组
前文提到,我们需要知道当s[i]!=t[j]时,我的模式串的哪一位同s[i]进行下一轮比较。我们设模式串的第j位和主串的第i位匹配失败时,模式串重新从第next[j]位开始和主串的第i位匹配,于是现在问题转化为我们如何求next[j]?
分析next[]数组的意义可以知道(令k=next[j]),next[j]表示模式t中第j位匹配失败时,模式t中第j位之前有k个字符和模式t开头的k个字符是匹配的,并且我们要使得k要最大,如若不然主串在小于i-k号位置之前就会有匹配成功的可能,于是我们可以得到k的值:k=max{k|1<=k<j且t[0…k-1]=t[j-k…j-1]}。但存在k不存在的情况和j=0的情况:当k不存在时,即是说模式t中第j位之前没有k个字符和模式t开头的k个字符是匹配的,故我们需要令j=0(即next[j]=0),让其从开头重新匹配;当j=0时,子串与主串失配,此时需要i++,特别的我们令next[0]=-1。这样,我们可以得到next[]的值。
通过递归的方法来求next数组的值
1)设next[0]=-1;
2)设next[0…j-1]都已经求得;
我们现在要求next[j]:
令k_0=next[j-1];k_p=next[k_(p-1)],则当t[k_p]=t[j-1]时,next[j]=k_p+1;(可以用数学归纳法证明)
void get_next(char *t,int next[])
{
next[0]=-1;//j=0
for(int j=1;t[j];j++)
{//循环终止条件为子串t中所有位置的next[]值已经求出来了
int k=next[j-1];//将k初始化为k0,即此时k为序列{kp}中最大的
while(k!=-1&&t[j-1]!=t[k])k=next[k];/*当t[j-1]!=t[k]时,利用k_p=next[k_(p-1)]
求出k_1,k_2,k_3…*/
next[j]=k+1;//当满足t[j-1]==t[k]时,knext[j]=k_p+1
}
}
于是,通过数组next[]指导我们失配时,j的位置,这样我们就能够更快速的模式匹配
#include<stdio.h>
void get_next(char *t,int next[])
{
next[0]=-1;//j=0
for(int j=1;t[j];j++)
{//循环终止条件为子串t中所有位置的next[]值已经求出来了
int k=next[j-1];//将k初始化为k0,即此时k为序列{kp}中最大的
while(k!=-1&&t[j-1]!=t[k])k=next[k];/*当t[j-1]!=t[k]时,利用k_p=next[k_(p-1)]
求出k_1,k_2,k_3…*/
next[j]=k+1;//当满足t[j-1]==t[k]时,knext[j]=k_p+1
}
}
void main()
{
char s[]="abcdabcdbccbbbccc";
char t[]="bcdbca";
int next[30];
get_next(t,next);//获得next数组的全部值
int i=0,j=0;
while(s[i]&&(j==-1||t[j]))
if(j==-1||s[i]==t[j])
{
i++;
j++;
}
else j=next[j];//当s[i]!=t[j]时,j从next[j]的位置开始匹配
if(!t[j]) printf("%d\n",i-j);
else printf("匹配失败!\n");
}
改进的KMP算法
void get_nextval(char t[],int nextval[])
{//获得数组nextval的递归算法
int j,k;
nextval[0]=-1;
for(j=1;t[j];j++)
{
k=t[j-1];
while(k!=-1&&t[k]!=t[j-1])
k=nextval[k];//前面的和获得next数组值的算法一样
if(t[j]==t[k+1])
nextval[j]=nextval[k];/*当t[j]和j将要滑向的位置的t[k+1]的值相等时,
j继续向下滑,即nextval[j]的值和nextval[k]的值
相等,然后再进行比较
*/
else
nextval[j]=k+1;
}
}