🚀 【考纲要求】字符串模式匹配
一、暴力模式匹配算法
问题描述: 给定一个主串Text = "abcdabcd"
,再给定一个串Parten = "abg"
。匹配算法要实现再主串Text
是否含有子串Parten
,若在主串Text中能找到子串Parten
,则返回在子串Parten
在主串Text
的起始位置。
暴力匹配的思想:
定义了两个变量。一个i
和一个j
,分别用于遍历数组Text
和数组Parten
。
Text[i]
中的内容与Parten[j]
中的内容比较,如相等就不断的i++
和j++
,依次继续往后比较,直到遇到Text[i] != Parten[j]
时,表示这次从Text的第一个元素开始匹配不成功,此时就会i
和j
的值就要进行会退操作。
j = 1; //j回到原来的位置
i = i - j + 2; //回到原来的位置加1的位置上
这里的i = i - j + 2;
,可以这样理解,i++
和j++
是同时加的,首先j
的值可以反应加了几次,即加了j-1
次,先让j
回到原来位置,即i = i- ( j- 1 ) = i - j + 1
,由于是让i
回到原来的位置加1的位置上 ,所以当Text[i]
中的内容与Parten[j]
中的内容不相等时,得让i = i - j + 2
。
一旦循环走完,就是j
的值大于串Parten
长度时,就表示已经找到了,返回子串Parten
在主串Text
的起始位置i-Parten.length
。如何理解在主串Text
的起始位置i-Parten.length
?,和上述i
回溯到原来的位置加1类似理解。匹配成功是j
走完了怎个子串Parten
,即j
加了子串Parten
的长度,对于其在主串Text
的起始当然就是位置i-Parten.length
,就是减去走了多少次。
#include<stdio.h>
#include<string.h>
// text存放主串
// p存放匹配串
int Index(const char* text, const char* p) {
int i = 1, j = 1;
int legth_text = strlen(text)-1; //减去空格
int legth_p = strlen(p) - 1; //减去空格
while (i<= legth_text && j<= legth_p) { //循环条件
if (text[i] == p[j]) { //相等就都++
++i;
++j;
}
else { //不相等就归位
i = i - j + 2; //i指向开始匹配的后一个
j = 1; //j指向开始的第一个
}
}
if (j > legth_p) {
return i - legth_p; //返回起始的位置
}
else { //由于j<=strlen(p)条件循环结束,表示没有找到,就是j都走完了也没匹配上
return 0;
}
}
int main() {
printf("%d\n", Index(" abcabcdfg", " ca")); //3
printf("%d\n", Index(" abcabcdfg", " abca")); //1
printf("%d\n", Index(" abcabcdfg", " bcab")); //2
return 0;
}
二、KMP算法
首先知道KMP算法有个大招就是next
数组,这个数组会给出你每一次匹配失败后,j
的返回位置,同时i
保持不变。先不理解KMP算法的原理,感受它的过程后就会自然的理解。简单来看下KMP算法的过程。
当往后循环比较的时候,此时Text[i]
中的内容与Parten[j]
中的内容不相等,按着刚刚说的暴力解法应该此时让j
回到原来的位置i
回到原来的位置加1的位置上 。而我们的KMP算法有大佬求解了一个非常牛逼的next
数组,该数组里面写好了每一次匹配失败之后j
的返回位置!!!,所以在匹配失败的时候直接按着next
中的内容返回写好的位置。(现在先不了解next
数组的求法,后面会给出,这里先给出计算好的此次next
数组,接着往下看它是如何运作的吧!)
此时Text[i]
中的内容与Parten[j]
中的内容不等,j
按着next
数组中的内容返回位置。此时不相等了,j
按着next
中的内容返回,即j=next[j]
,i
不动,然后依次下去就能得到结果。(可以手动试试,这里不用管next
数组怎么来的,后面会说,跟着过程体验一遍。)
#include<stdio.h>
#include<string.h>
int Index2(const char* text, const char* p) {
int i = 1, j = 1;
int next[7] = { -1, 0, 1, 1, 2, 3, 1 };
int legth_text = strlen(text) - 1; //减去空格
int legth_p = strlen(p) - 1; //减去空格
while (i <= legth_text && j <= legth_p) { //循环条件
if (j==0 || text[i] == p[j]) { //相等就都++
++i;
++j;
}
else { //不相等就归位
j = next[j]; //j由next数组确定
}
}
if (j > legth_p) {
return i - legth_p; //返回起始的位置
}
else { //由于j<=strlen(p)条件循环结束,表示没有找到,就是j都走完了也没匹配上
return 0;
}
}
int main() {
printf("%d\n", Index2(" abababcd", " ababcd")); //3
return 0;
}
如上代码,我们得到了同样的结果,其实这就是采用KMP算法进行模式匹配的,只不过到现在为止,还没有真正的了解KMP算法的核心步骤,其核心就是next
数组的求法,现在就开始学习其是如何求解next
数组的。
三、KMP算法的核心----求next数组
这里的求解next的过程讲解只是一个浅显的理解,更多的是应试,要想深刻理解里面的求解步骤,及其原理过程的话,推荐以下一个视频,讲解的十分好。
【【完整版】终于有人讲清楚了KMP算法,Java语言C语言实现】
https://www.bilibili.com/video/BV1UL411E7M8/?share_source=copy_web&vd_source=c0ec1c9d72932bb83637199ead965f65
Parten[j]
为a、b、a、b、c、d,next数组就是基于待匹配串求出来的,求next的关键:找到匹配成功部分的两个相等的真子串(不包含本身),以第一个字符开始,一个以j-1下标处的字符结尾。
next
数组第一个和第二个只写-1、0- 当在
Parten
的第3个元素a
处发生不匹配时候,找Parten
的第三个元素前序列的两个相等的真子串(找到匹配成功部分,即第3个元素前都是匹配的),无相等的真子串,写0。 - 当在
Parten
的第4个元素b
处发生不匹配时候,找Parten
的第四个元素前序列的两个相等的真子串,即a
子串和a
子串,字串的元素个数为1,就写1。 - 当在
Parten
的第5个元素c
处发生不匹配时候,找Parten
的第五个元素前序列的两个相等的真子串,即a b
子串和a b
子串,字串的元素个数为2,就写2。 - 当在
Parten
的第6个元素d
处发生不匹配时候,找Parten
的第六个元素前序列的两个相等的真子串,无相等的真子串(可能会觉得有a b
子串和a b
子串,但是要求以第一个字符开始,一个以j-1下标处的字符结尾,此时j-1下标处的字符为c),写0。
**这样就求出来了next数组!!!!最后还需要将next数组中的数值都加1,就真的得到了最后的next数组。**即为[0,1,1,2,3,1]
;也就是上面我们使用的。这是手动求next
数组,但是对于代码来说,是推导了其数据过程,最后得出其递推的公式,然后再编写求解next数组的代码的。
代码实现求next数组:
#include<stdio.h>
#include<string.h>
void get_next(const char* p, int next[]) {
int i = 1, j = 0;
next[0] = -1;
next[1] = 0;
int len = strlen(p)-1; //减去第0个位置处的长度
while (i<len) {
if (j == 0 || p[i] == p[j]) {
i++;
j++;
next[i] = j; //p[i] == p[j]时,next[j+1]=next[j]+1
}
else {
j = next[j]; //令j = next[j],循环继续
}
}
}
int main() {
//printf("%d\n", Index2(" abababcd", " ababcd")); //3
int next[7];
get_next(" ababcd", next);
printf("求得的next如下:\n");
for (int i = 1; i < 7;i++) {
printf("%d ", next[i]);
}
printf("\n");
return 0;
}
现在只需要把函数int Index2(const char* text, const char* p)
中修改成使用上述函数求next
值就可以了,到这里KMP算法就完成了!!!
int next[7] = { -1, 0, 1, 1, 2, 3, 1 };
//改成
int Index2(const char* text, const char* p, int next[] ) //从外部传进函数