BF算法与KMP算法实现
BF
算法全称为
Brute Force
算法,是一种普通的字符串匹配算法;
KMP
算法全称为
Knuth-Morris-Pratt
算法,是一种改进的字符串匹配算法。两者作用效果一样,区别只在于效率高低的问题。
假设有主串
S
=
"
abcabcabdabcabc
",子串
T="abd"
。要判断主串S中是否含有子串T,且告知其位置。就是这样的一个问题促使人们发展出这两种算法。
回到问题,如果用数组保存两个字符串,可以发现S中含有T,判断为真,且位置为下标
6->8
,即
S[6]==T[0];S[7]==T[1];S[8]==T[2]。即S="abcabcabdabcabc"。
下面使用图片的方式分别对BF算法和KMP算法的流程进行说明。
BF算法
从主串S的索引0处和子串的索引0处进行匹配,遇到第一个不匹配的位置停下
从主串S的索引1处和子串T的索引0处进行匹配,遇到第一个不匹配的位置停下
从主串S的索引2处和子串T的索引0处进行匹配,遇到第一个不匹配的位置停下
从主串S的索引3处和子串T的索引0处进行匹配,遇到第一个不匹配的位置停下
从主串S的索引4处和子串T的索引0处进行匹配,遇到第一个不匹配的位置停下
从主串S的索引5处和子串T的索引0处进行匹配,遇到第一个不匹配的位置停下
从主串S的索引6处和子串T的索引0处进行匹配,直到子串遍历完也没有遇到不匹配的问题,说明匹配成功
通过这一系列的图,可以很明白的看出,子串T在主串S下移动,同时对比每个相对的元素。如果相同,进行下一位对比;如果不等,即遇到第一个不等情况时,立即将子串T的索引移动到0处,进行下一轮的对比。
下面作者将完整代码贴出来,仅供参考。
#include
#include
#include
#define S_SIZE 100
#define T_SIZE 50
using namespace std;
void BF(char *S,char *T);
int main()
{
char S[S_SIZE] = {'\0'};
char T[T_SIZE] = {'\0'};
cout<<"请输入主串:"<"<
KMP算法
下面说说经过优化而产生的KMP算法,现在教科书大部分已经不在讲解KMP算法了,究其缘由就是其比较难理解。
KMP算法之所以较之BF算法优化,要先从时间复杂度说起。简而言之,若S长度为m,T长度为n,则BF时间复杂度为O(m×n),而KMP时间复杂度为(m+n),可见一斑。这里关于时间复杂度不做详细说明。
下面通过简化图进行感性认识一下KMP算法的流程。
从主串S索引0处和子串T索引0处开始匹配。
匹配到索引5时,出现读一个不匹配位置,停止。
直接将T溯回到索引3,而不是溯回到索引0,再进行正常匹配。
仔细看图,且不必去管其中的Next项的意义,且听作者徐徐道来。
步骤一中从主串S的索引0处和子串的索引0处开始比对。然后直到在S的索引5和T的索引5处遇到第一个不匹配点索引5处停止。下一步从主串S的索引5处和子串T的索引2处开始。注意,这里子串不是从0开始了,是从2开始进行比对,于是就达到了减少运行时间的目的。
为什么可以直接从子串T的索引2处进行比对呀?原因就在于子串T的特殊结构。图中T = "aasaay",其中有相同的部分,T[0]==T[1]==T[3]==T[4]。间接地进行了对比。
有点明白了吗?
尽可能的使子串的回溯过程减少。通过利用子串的特殊模式或者说是特征来减少子串T的不必要回溯,来尽可能的减少运行时间。而BF算法中的回溯都是统一回溯到索引0处的位置。但KMP算法却可以尽可能的回溯到下标较大的位置来减少不必要的回溯。
那么究竟要回溯到哪里呢?这里就需要额外的存储空间来记录这个下标位置了。这里称为“模式值”---Next。如果两个字母不相等了,就可以直接移动到Next的值所指的位置处。
然而,关于Next的值究竟是多少?这是KMP算法的核心内容。
依然是引用百度百科的条目:
(1)Next[0]=-1,任何串的第一个字符的模式值规定为-1。
(2)Next[j],模式串中下标为j的字符,如果与首字母相同,且j的前面的1-k个字符与开 头的1-k个字符不等(或者相等但T[k]==T[j])(1<=k
(3)Next[j]=k,模式串T中下标为j的字符,如果j的前面k个字符与开头的k个字符相 等,且T[j]!=T[k](1<=k
(4)Next[k]=0,除(1)(2)(3)的其他情况。
依然是看不懂,然而作者自己悟到了一幅图。归结起来就一句话:要求移动到最近的可用位置,能少移动就少移动。请看下图:
从主串S索引0处和子串T索引0处进行匹配
遇到第一个不匹配的位置,停止检查
开始推测最短的回溯距离。回溯一个单位时,从S索引1和T索引0处开始对比,出现第一个不匹配时停止或者索引到4之后停止,即上一步停止的S索引处。
回溯两个单位时,从S索引2和T的索引0处开始对比,出现第一个不匹配时停止或者索引到4之后停止。
回溯三个单位时,从S索引3和T的索引0处开始对比,直到S索引4之后仍未出现不匹配,回溯成功!
可能代码写起来有点困难,但还是写出来了。如下是计算模式值Next的函数,也是KMP算法之核心:
int NextOfKMP(char *T,int n,int *Next)
{
int i;
if(n == 0)
{
Next[n] = -1;
return 1;
}
//开始历遍m为移动偏移量
for(int m=1;m<=n;m++)
{
if(m == n && T[0] != T[n])
{
Next[n] = 0;
return 1;
}
if(m == n && T[0] == T[n])
{
Next[n] = -1;
return 1;
}
i = 0;//从0遍历
while((i+m) != n)
{
if(T[i] != T[i+m])
break;
++i;
}
if(((i+m) == n) && (T[i] != T[n]))
{
Next[n] = i;
return 1;
}
}
}
最后,把一个完整的程序贴出来作为本篇博文之结束,同时也希望诸位能够批评指正。
#include
#include
#include
#define S_SIZE 100//主串S长度
#define T_SIZE 50//子串T长度
using namespace std;
int KMP(char *S,char *T);
int NextOfKMP(char *T,int n,int *Next);
int main()
{
char S[S_SIZE] = {'\0'};
char T[T_SIZE] = {'\0'};
cout<<"请输入主串:"<"<