串就是很多字符组在一起,就是一个字符数组。串的操作不是很复杂,但是查找子串却很麻烦。
我是参考大话数据结构这本书学习的,看了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;
}