数据结构里KMP是考点之一,很多人也觉得是难点之一。首先是代码很简洁,几行代码就完成了工作让人摸不着头脑;其次是想象不出KMP匹配的具体过程。以下是数据结构KMP的匹配过程(多图预警):
先给大家看过程图,步骤解析后方:
举个简单的例子:
首先我们要获取next数组(获取next数组的步骤解释写在这里数据结构——KMP之next执行过程解析,就不在本文说明了)。
模板串的next数组(下标从1开始):
a | b | a | b | d |
---|---|---|---|---|
0 | 1 | 1 | 2 | 3 |
1 | 2 | 3 | 4 | 5 |
第一步:
cababacababdcdabc
ababd
|
当前比较的字符:c a
字符不匹配,j = next[1] = 0;
模板串直接右移一位;
第二步
j=0,所以 j 和 i 都自加一;
第三步
cababacababdcdabc
ababd
|
当前比较的字符:a a
s.ch[i]==ts.ch[j],j 和 i 自加一继续匹配;
第四步
cababacababdcdabc
ababd
|
当前比较的字符:b b
s.ch[i]==ts.ch[j],j 和 i 自加一继续匹配;
(第五步也是字符相等略)
第六步
cababacababdcdabc
ababd
|
当前比较的字符:b b
s.ch[i]==ts.ch[j],j 和 i 自加一继续匹配;
第七步
cababacababdcdabc
ababd
|
当前比较的字符:a d
此时 j = 5 != 0,字符又不相等,j = next[5] = 3;
模板串滑动,直接拿第3号位置的字符与目标串当前第i个字符匹配(直接向前滑动了两位)。
第八步
cababacababdcdabc
ababd
|
当前比较的字符:a a
s.ch[i]==ts.ch[j],j 和 i 自加一继续匹配;
第九步
cababacababdcdabc
ababd
|
当前比较的字符:c b
此时 j = 4 != 0,字符又不相等,j = next[4] = 2;
模板串滑动,直接拿第2号位置的字符与目标串当前第i个字符匹配(向前滑动1位)。
第十步
cababacababdcdabc
ababd
|
当前比较的字符:c b
此时 j = 2 != 0,字符又不相等,j = next[2] = 1;
模板串滑动,直接拿第2号位置的字符与目标串当前第i个字符匹配(向前滑动1位)。
(第11、12、13步依然是向前滑动(因为 j = 0了,i 和 j都会自加一))
第十四步
cababacababdcdabc
ababd
|
当前比较的字符:a a
s.ch[i]==ts.ch[j],j 和 i 自加一继续匹配;
(往后一直到第十八步就都是匹配的了)
第十八步
cababacababdcdabc
ababd
|
当前比较的字符:d d
s.ch[i]==ts.ch[j],j 和 i 自加一继续匹配;
此时 j = 5+1 = 6,超出了模板串的长度了,跳出while循环,返回匹配标识(匹配成功就返回匹配子串的起点,计算方法:子串末尾元素在目标串的位置减去模板串长度。satrt = i - template.length)
匹配结束。
关于核心的KMP函数
int KMP(S s,S ts,int next[]){
//s是目标串,t是模板串
int i = 1;//对应目标串下标
int j = 1;//对应模板串下标
while(i<=s.length&&j<=ts.length){
showProcess(s,ts,i,j);
if(j==0||s.ch[i]==ts.ch[j]){
i++;
j++;
}else{
j = next[j];//j回头
}
}
if(j>ts.length){//如果走完了整个模板串,说明模板串是目标串的子串
return i-ts.length;
}else{
return 0;
}
}
参数说明:
s为目标串;ts为模板串
i是目标串的下标,ts是模板串和next数组对应的下标;
j = next[j]:指的是将模板串的第 j 个元素拉过来和目标串的第 i 个元素进行比较(滑动模板串)
可视化输出函数:
void showProcess(S s,S ts,int i,int j){
for(int ti=1;ti<=s.length;ti++){
cout<<s.ch[ti];
}cout<<endl;
for(int tj=1;tj<=i-j;tj++){
cout<<" ";
}
for(int tj=1;tj<=ts.length;tj++){
cout<<ts.ch[tj];
}cout<<endl;
for(int ti=1;ti<i;ti++){
cout<<" ";
}cout<<"|"<<endl;
cout<<"当前比较的字符:"<<s.ch[i]<<" "<<ts.ch[j];
cout<<endl<<endl<<endl;
}
主要是关于模板串的前方有多少个空格(表示滑动了多少位):
模板串前方的空格数 = i - j;
指示标“|”前方的空格数 = i;
源码
/*
广西师范大学 计算机科学与工程学院
GuangXi Normal University
College of Computer Science and Engineering
Student STZ
*/
#include<iostream>
#include<stdio.h>
using namespace std;
typedef struct Str{
char *ch;
int length;
}S;
void showStr(S s){
for(int i=0;i<s.length;i++){
cout<<s.ch[i+1]<<" ";
}
cout<<endl;
}
int initial(S &s,int length){
s.ch = (char*)malloc(sizeof (char)*(length+2));//多加一个位置给'\0'
if(s.ch==NULL){
cout<<"初始化失败\n";
return 0;
}
s.length = length;
return 1;
}
int insertStr(S &s,char *ch){
int length = 0;//记录字符串数组ch的长度
char *p = ch;//声明一个指针p指向字符串数组ch
while(*p){
length++;//注意,这个length不会算入最后的'\0'进去
p++;
}
if(!length){//如果ch为空
s.ch == NULL;
s.length = 0;
return 0;
}
if(initial(s,length)){//初始化串
p = ch;//指针p重新指向字符串ch的起点
for(int i = 0;i<=length;i++){//用=号是为了将末尾的'\0'复制出来
s.ch[i+1] = *p;
p++;
}
return 1;
}
return 0;
}
void getNext(S &s,int next[]){
int i = 1;
int j = 0;
next[1] = next[0] = 0;
while(i<s.length){
if(j==0||s.ch[i]==s.ch[j]){
i++;
j++;
next[i] = j;
}else{
j = next[j];//
}
}
}
void showProcess(S s,S ts,int i,int j){
for(int ti=1;ti<=s.length;ti++){
cout<<s.ch[ti];
}cout<<endl;
for(int tj=1;tj<=i-j;tj++){
cout<<" ";
}
for(int tj=1;tj<=ts.length;tj++){
cout<<ts.ch[tj];
}cout<<endl;
for(int ti=1;ti<i;ti++){
cout<<" ";
}cout<<"|"<<endl;
cout<<"当前比较的字符:"<<s.ch[i]<<" "<<ts.ch[j];
cout<<endl<<endl<<endl;
}
int KMP(S s,S ts,int next[]){
//s是目标串,t是模板串
int i = 1;//对应目标串下标
int j = 1;//对应模板串下标
while(i<=s.length&&j<=ts.length){
showProcess(s,ts,i,j);
if(j==0||s.ch[i]==ts.ch[j]){
i++;
j++;
}else{
j = next[j];//j回头
}
}
if(j>ts.length){//如果走完了整个模板串,说明模板串是目标串的子串
return i-ts.length;
}else{
return 0;
}
}
//ABABDABACDABABCABAB
//ABABCABAB
/*
cababacababdcdabc
ababd
*/
int main(){
Str ts,s;
char ch1[100],ch2[100];
cout<<"目标串为:"<<endl;
scanf("%s",&ch2);
insertStr(s,ch2);
cout<<"模板串为:"<<endl;
scanf("%s",&ch1);
insertStr(ts,ch1);
int next[100];
getNext(ts,next);
for(int i = 1;i<=ts.length;i++)
cout<<next[i]<<" ";
cout<<endl;
cout<<"\n";
int k = KMP(s,ts,next);
if(k){
cout<<"success! "<<k<<"\n";
}else{
cout<<"fault!\n";
}
return 0;
}
换个例子: