串的kmp模式匹配

串就是很多字符组在一起,就是一个字符数组。串的操作不是很复杂,但是查找子串却很麻烦。

我是参考大话数据结构这本书学习的,看了n遍,基本思想是懂了(但是还没透彻),我把我所能理解的记录下来。

 

 

先说传统的也就是正常思维都能想出来的方法,实现也很简单。先从概念上理解,假设我们有一个字符串abcdefgh,有一个子串ef那么我们平常应该怎么想。肯定是把ef拿过去一个一个的比,

第一个比较不相等,哦,那么我们就向后移一位,直到我们比较到ef,然后得出相等就是匹配成功了。

#define MAXLEN 100

typedef char SString[MAXLEN+1];

//====================================

// 使字符数组转换为字符串

bool ToString(SString s, char *chars) {

         inti;

         if(strlen(chars)> MAXLEN + 1)

                   returnfalse;

         else{

                   s[0]= strlen(chars);//0位存储字符串的长度

                   for(i= 1; i <= s[0]; i++)

                            s[i]= chars[i - 1];//赋值

                   returnfalse;

         }//else

}//ToString

//==========================================

//传统的子串比较方法

int Index(SString s,SString s1, int pos ) {

         inti = pos;

         intj = 1 ;

         while(i<= s[0] && j <= s1[0]) {//s[0],s1[0]分别为俩串的长度

                   if(s[i]== s1[j]) {

                            i++;

                            j++;//匹配的话就继续

                   }

                   else{

                            i= pos++;//不匹配的话i就从开始的下一个位置继续

                            j= 1;

                   }//else

         }//while

//      if(j> s1[0])//这样写是因为如果匹配了,那么跳出时j为s1[0] + 1,而i还没到头

//               returni - s1[0];//从匹配到的那个位置开始走了s1[0]个位置

//      else

//               return0;

         if(i> s[0] ) //这样写是当一直比较直到i跳出也不匹配时,i=s[0] + 1;

                   return0;

         else

                   returni - s1[0];

}//Index

 

这上面就是传统的匹配算法,理解起来也蛮简单的,(记住0位置存放的是字符串的长度),如果相等,那么i++,j++;

如果不相等的话,i返回头部,从第二个位置从新开始和子串比较。比较完跳出,我们这样想,什么时候会跳出了,我们拿一个子串和主串比较,比啊比,比到主串的某个位置,一直往后到尾巴,发现子串还没比完,这时候就跳出了。

还可以理解为我们比成功跳出了,也就是子串走完了主串没走完。那么这时候j = s1[0]+1

好了,传统匹配应该就这样了,再说KMP模式匹配。

 

KMP的思想就是把我们常规思想中的冗余部分给去除了。其实我们常规想法上有很大部分是重复比较了。比如主串ababaabaa,子串baa,我们怎么去比较(匹配不相等红色标记)


而KMP算法就是去掉这些多余的步骤。先贴上代码

 

//===================================================

//KMP模式匹配算法

 

//求子串的next数组

void get_next(SString s1, int *next) {

         inti = 1;

         intj = 0;

         next[1]= 0;//一个字符前面谈不上有前缀

         while(i<= s1[0]) {

                   if(0== j || s1[i] == s1[j]) {

                            i++;

                            j++;

                            next[i]= j;//记录变化的j值

                   }//if

                   else{

                            j= next[j];//回溯

                   }//else

         }//while

}//get_next

//========================================

int Index_KMP(SString s, SString s1, intpos) {

         inti = pos;//从哪个位置开始比较,一般都从头开始

         intj = 1;

         intnext[MAXLEN];

         get_next(s1,next);//得到子串next数组

         while(i<= s[0] && j <= s1[0]) {

                   if(0== j || s[i] == s1[j]) {

                            i++;

                            j++;

                   }//if

                   else{

                            j= next[j];//回溯

                   }//else

         }//while

         if(j> s1[0])

                   returni - s1[0];

         else

                   return0;

 

}//Index_KMP

突然发现语塞了,该怎么说。我想说解释next数组的含义与得到方法,大话数据结构书里讲的蛮好的。

KMP就是根据next数组的值来避免不必要的回溯(就是上面的i一直++,不要在往回从头来过了);

Next数组是用来存储j的变化值的(看看与传统的有啥不一样),说白了 ,传统中我们每次i,j都回溯了,现在只让子串回溯。那么next[j]是什么意思了。

可以说是子串中前缀与后缀相同的字符数(加1),比如前面说过的


然后我们从代码角度思考怎么得到。

void get_next(SString s1, int *next) {

         inti = 1;

         intj = 0;

         next[1]= 0;//一个字符前面谈不上有前缀

         while(i<= s1[0]) {

                   if(0== j || s1[i] == s1[j]) {//开始i是比j多一位的

                            i++;

                            j++;

                            next[i]= j;//记录变化的j值

                   }//if

                   else{

                            j= next[j];//回溯

                   }//else

         }//while

}//get_next

首先i是用来遍历子串的,j求前后缀相同字符数也就是next值。比如abab,我们i++,j++来遍历子串。可以说get_next()这个函数有点像KMP整体算法。而这个next[]存放的是子串的字符下标。因为开始next[1] = 0了,然后进入循环,next存入的是1,也就是a的下标(再次申明a前面存的是串的长度),这时候我们可以说j代表的是前缀这种串的游标,只要找到前后缀相同,那么j就向前遍历前缀,如果一旦发现前后缀不同,j就重新找前缀。

如果搞懂了next,KMP算法就不是很难了,很传统不一样的地方,i不回溯了,j回溯到的地方也不是到子串的头了,而是像我们上面说的尺子那样了。

 

觉得有点乱,也差不多只有我自己懂。总结下,我认为KMP优化的有俩方面,一方面是i不回溯了(回溯,就是回到前面),因为前面已经说过,在第一次遍历比较时,已经把子串后面的都比较过了。所以我们就让i不回溯呗,那不就ok了么,问题来了,这样


#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
#include <string.h>

#define MAXLEN 100
typedef char SString[MAXLEN+1];
//====================================
// 使字符数组转换为字符串
bool ToString(SString s, char *chars) {
	int i;
	if(strlen(chars) > MAXLEN + 1)
		return false;
	else {
		s[0] = strlen(chars);//0位存储字符串的长度
		for(i = 1; i <= s[0]; i++)
			s[i] = chars[i - 1];//赋值
		return false;
	}//else
}//ToString
//==========================================
//传统的子串比较方法
int Index(SString s,SString s1, int pos ) {
	int i = pos;
	int j = 1 ;
	while(i <= s[0] && j <= s1[0]) {//s[0],s1[0]分别为俩串的长度
		if(s[i] == s1[j]) {
			i++;
			j++;//匹配的话就继续
		}
		else {
			i = pos++;//不匹配的话i就从开始的下一个位置继续
			j = 1;
		}//else
	}//while
//	if(j > s1[0])//这样写是因为如果匹配了,那么跳出时j为s1[0] + 1,而i还没到头
//		return i - s1[0];//从匹配到的那个位置开始走了s1[0]个位置
//	else 
//		return 0;
	if(i > s[0]) //当一直比较直到i跳出也不匹配时,i=s[0] + 1;
		return 0;
	else
		return i - s1[0];
}//Index

//===================================================
//KMP模式匹配算法

//求子串的next数组
void get_next(SString s1, int *next) {
	int i = 1;
	int j = 0;
	next[1] = 0;//一个字符前面谈不上有前缀
	while(i <= s1[0]) {
		if(0 == j || s1[i] == s1[j]) {
			i++;
			j++;
			next[i] = j;//记录变化的j值
		}//if
		else {
			j = next[j];//回溯
		}//else
	}//while
}//get_next
//========================================
int Index_KMP(SString s, SString s1, int pos) {
	int i = pos;
	int j = 1;
	int next[MAXLEN];
	get_next(s1,next);//得到子串next数组
	while(i <= s[0] && j <= s1[0]) {
		if(0 == j || s[i] == s1[j]) {
			i++;
			j++;
		}//if
		else {
			j = next[j];//回溯
		}//else
	}//while
	if(j > s1[0]) 
		return i - s1[0];
	else
		return 0;

}//Index_KMP


int main() {
	SString s,s1;
	char c[MAXLEN + 1];
	printf("输入主串s:\n");
	gets(c);
	ToString(s,c);
	printf("输入子串s1:\n");
	gets(c);
	ToString(s1,c);
	
	int k = Index(s,s1,1);
	if(0 == k ) {
		printf("不匹配\n");
	}
	else {
		printf("匹配的位置是%d\n",k);
	}
	printf("KMP模式匹配算法:\n");
	int next[MAXLEN];
	int m = Index_KMP(s,s1,1);
	if(0 == m) {
		printf("不匹配\n");
	}
	else {
		printf("匹配的位置是%d\n",m);
	}
	return 0;
}


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值