数据结构——KMP匹配过程(可视化输出)

数据结构里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;
} 

换个例子:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_51231048/article/details/125835067
今日推荐