数据结构--字符串匹配算法(BF,KMP)

什么是字符串匹配

  • 两个字符串A和B,判断B是否是A的字串,并返回B再A中第一次出现的位置 如下图
    数据结构--字符串匹配算法(BF,KMP)_第1张图片
  • 返回3
    数据结构--字符串匹配算法(BF,KMP)_第2张图片
  • 没有匹配的 返回0

BF

  • 我们很容易想出一种简单粗暴的方式,就是从主串开始,把字符串A和字符串B的字符逐个比较 如果出现不相同的 如第一个图片
  • 把字符串B向后移动一位,从字符串A的第二位开始,把字符串A和字符串B的字符逐个比较
  • 总结来说 我们可以i,j来分别记录字符再字符串A、B中的位置,当两个字符相同时则i++,j++,但不相同时则i=i-j+1,j=0
  • 这种方法很好理解就是纯暴力(Brute Force)
  • 下面上代码
//BF
#include
#include 
int BF(char M[],char P[]){
	int i,j;
	for(i=0,j=0;i<strlen(M)&&j<strlen(P);){
		if(M[i]==P[j]){
			i++;
			j++;
		}
		else{
			i=i-j+1;
			j=0;
		}
	}
	if(j>=strlen(P))
		return i-j+1;//字符串的存储是从第0位开始的
	return 0;
	
}

int main(){
	char Main[20]="abbcefghbce";
	char Pattern[20]="bce";	
	if(BF(Main,Pattern)!=0){
		printf("匹配成功!首次出现的位置是:%d\n",BF(Main,Pattern));
	}
	else{
		printf("匹配失败!\n");
	}
	return 0;
}
  • 但这种算法的时间复杂度很高 如果是特殊情况 如:
    字符串A:aaaaaaaaaaaaaaaaaaab
    字符串B:aaab
    这样来看,我们就会做很多无用功
  • BF算法的最坏时间复杂度是O(mn)

KMP

  • 有那么一种算法,它的目标是让模式串(我们把字符串A叫主串字符串B叫模式串)在每一轮尽量多移动几位,从而减少无谓字符串的比较
  • kmp算法的精髓就是构造一个数组(前缀表)来存储最长公共前后缀(模式串的)
  • 我们需要一个一个串的来找最长公共前后缀,这个最长是不包括自己本身的,具体可类比数学当中的真子集
  • 具体可看下图
    数据结构--字符串匹配算法(BF,KMP)_第3张图片
  • 如图右侧的棕色字体就是前缀表了,代码实现如下:
//pattern[]为模式串
//prefix[]是存储前缀表的数组
//n是模式串的长度
//i指向字串的长度,len是之前最长公共前后缀的下一个位置
void prefix_table(char pattern[],int prefix[],int n){
	prefix[0]=0;
	int len=0;
	int i=1;
	while(i<n){
	//如果最长公共前后缀的下一个位置和新加的字符一样的话,那么他所对的前缀表只需+1即可
	//如ABCBA是2那么ABCABC就是3
		if(pattern[i]==pattern[len]){
			len++;
			prefix[i]=len;
			i++;
		}
		//如果不相等,我们就把len的位置移到它前一个字符所对的前缀表所指的位置上去
		else{
			if(len>0){
				len=prefix[len-1];
			}
			//如果当len已经为0时,再进行--就会发生数组越界的情况并且第0个字符所对的前缀表的值恰好为0,即为len,所以我们进行下面的操作
			else{
				prefix[i]=len;
				i++;
			}
		}
	}
}
  • 一般来说我们对于这个前缀表数组所进行的操作就是把最后一位删去,在第一位的前面添上0,即将数组整体向后移动一位,prefix[0]=0;操作如下:
void move_prefix_table(int prefix[],int n){
	int i;
	for(i=n-1;i>0;i--){
		prefix[i]=prefix[i-1];
	}
	prefix[0]=-1;
}
  • 现在,我们所有的准备工作已经做完,接下来就可以进行KMP算法了
void kmp_search(char text[],char pattern[]){
	int i=0,j=0;
	int n=strlen(pattern);
	int m=strlen(text);
	//动态创建一个存储前缀表的数组
	int* prefix=(int *)malloc(n*sizeof(int));
	prefix_table(pattern,prefix,n);//生成前缀表
	move_prefix_table(prefix,n);//移动前缀表
	
	//text[i]     len(text)=m
	//pattern[j]  len(pattern)=n
	
	while(i<m){
	//当找到符合要求的位置时,则输出,然后继续向下找
		if(j==n-1&&text[i]==pattern[j]){
			printf("Found pattren at %d\n",i-j+1);
			j=prefix[j];
		}
		if(text[i]==pattern[j]){
			i++;
			j++;
		}
		else{
			j=prefix[j];
			if(j==-1){
				i++;
				j++;
			}			
		}
	} 
}

完整代码

#include
#include
#include
#include
void prefix_table(char pattern[],int prefix[],int n){
	prefix[0]=0;
	int len=0;
	int i=1;
	while(i<n){
		if(pattern[i]==pattern[len]){
			len++;
			prefix[i]=len;
			i++;
		}
		else{
			if(len>0){
				len=prefix[len-1];
			}
			else{
				prefix[i]=len;
				i++;
			}
		}
	}
}

void move_prefix_table(int prefix[],int n){
	int i;
	for(i=n-1;i>0;i--){
		prefix[i]=prefix[i-1];
	}
	prefix[0]=-1;
}

void kmp_search(char text[],char pattern[]){
	int i=0,j=0;
	int n=strlen(pattern);
	int m=strlen(text);
	int* prefix=(int *)malloc(n*sizeof(int));
	prefix_table(pattern,prefix,n);
	move_prefix_table(prefix,n);
	
	//text[i]     len(text)=m
	//pattern[j]  len(pattern)=n
	
	while(i<m){
		if(j==n-1&&text[i]==pattern[j]){
			printf("Found pattren at %d\n",i-j+1);
			j=prefix[j];
		}
		if(text[i]==pattern[j]){
			i++;
			j++;
		}
		else{
			j=prefix[j];
			if(j==-1){
				i++;
				j++;
			}			
		}
	} 
}

int main(){
	char text[]="AABABCABAABABCABAACCABC";
	char pattern[]="ABABCABAA";
	
	kmp_search(text,pattern);

	
	return 0;
}

运行结果

在这里插入图片描述

  • 如果还有不明白的,请学习大佬的
  • KMP字符串匹配算法1
  • KMP字符串匹配算法2

你可能感兴趣的:(数据结构--字符串匹配算法(BF,KMP))