
目录
4.2_1 朴素模式匹配算法
一、什么是字符串的模式匹配
二、朴素模式匹配算法
(1)使用字符串基本 *** 作来实现
(2)通过数组下标来实现
总结:
4.2_2 KMP算法
一、KMP算法由来
二、朴素模式匹配算法
三、朴素模式匹配算法优化思路
四、如何用代码实现KMP匹配过程
4.2_3 KMP算法_求 next 数组
一、手算
二、KMP算法(next数组)完整代码
4.2_4 KMP算法的进一步优化
一、next 数组的优化思路
二、KMP算法(nextval数组)完整代码
4.2_1 朴素模式匹配算法 一、什么是字符串的模式匹配
在一段文字中搜索某一段内容。
主串:被搜索的字符串。
模式串:想要搜索的内容。
字符串模式匹配:在主串中找到与模式串相同的子串,并返回其所在位置。
* 子串——主串的一部分,一定存在;
模式串——不一定能在主串中找到;
二、朴素模式匹配算法因为模式串T的长度为6,所以就把主串S中长度为6的字符串与模式串T一一对比。
主串长度为n,模式串长度为m。
朴素匹配模式算法:将主串中所有长度为m的子串依次与模式串对比(最多对比n-m+1个子串),直到找到一个完全匹配的子串,或所有的子串都不匹配为止。
(1)使用字符串基本 *** 作来实现上一节代码实现中Index(S, T)函数就是用的这种算法。
// (8)求子串。用Sub返回串S的第pos个字符起长度为len的字符串
bool SubString(SString &Sub, SString S, int pos, int len){
// 子串范围越界
if(pos+len-1 > S.length){
return false;
}
// 把S的子串一个个填进Sub字符串里,并给Sub长度赋值
for(int i = pos; i < pos+len; i++){
Sub.ch[i-pos+1] = S.ch[i];
}
Sub.length = len;
return true;
}
// (9)比较操作。若S>T,则返回值>0;若S=T,则返回值=0;若S
(2)通过数组下标来实现
不使用字符串的基本 *** 作,直接通过数组下标实现朴素模式匹配算法。
(1)S[i]与T[j]字符进行比较,看是否匹配
(2)若当前子串匹配失败,则主串指针i指向下一个子串的第一个位置,模式串指针j回到模式串的第一个位置。
i = i - j + 2;
j = 1;
(3)若j > T.length,则当前子串匹配成功,返回当前子串第一个字符的位置(i - T.length)
运行结果:
实现代码:
// 20220605 朴素模式匹配算法
#include
#include
using namespace std;
// 串的静态数组存储
#define MAXLEN 255
typedef struct{
char ch[MAXLEN]; // 串中字符存储(从下标1开始存)
int length; // 串长度
}SString;
// 朴素模式匹配
int Index(SString S, SString T){
int i = 1, j = 1;
while(i <= S.length && j <= T.length){
cout << "S.ch[i]:" << S.ch[i] << ",T.ch[j]:" << T.ch[j] << endl;
if(S.ch[i] == T.ch[j]){ // 如果匹配就两个指针后移,让下一个字符开始比较
++i;
++j;
}else{
i = i - j + 2; // 主串指针回退,退到下一次该匹配的位置
j = 1; // 模式串指针回退,退到第一个字符
}
}
if(j > T.length){
cout << "主串中子串位置为:" << i-T.length << endl;
return i - T.length; // 匹配成功
}else{
cout << "未在主串中查找到子串位置!" << endl;
return 0;
}
}
// 串的赋值
bool StrAssign(SString &T, string chars){
T.length = chars.length();
for(int i = 1; i <= T.length; i++) T.ch[i] = chars[i-1];
return true;
}
void Printch(SString S){
for(int i = 1; i <= S.length; i++){
cout << S.ch[i];
}
}
int main(){
cout << "Hello Madoka!" << endl;
// 两个串的声明
SString S, T;
// 两个串赋值,S主串,T子串
StrAssign(S, "Do you like Madoka?");
StrAssign(T, "Madoka");
// 输出测试
cout << "主串值:" << endl;
Printch(S);
cout << endl << "主串长度:" << S.length << endl;
cout << "子串值:" << endl;
Printch(T);
cout << endl << "子串长度:" << T.length << endl;
// 朴素模式匹配
cout << "子串在主串中的位置为:" << Index(S, T) << endl;
return 0;
}
时间复杂度分析:
设主串长度为n,模式串长度为m,
最坏时间复杂度 = O((n-m+1) m) = O( ) = O(nm)
* 为什么省掉——因为很多时候主串n的长度远远大于子串m的长度
总结: 4.2_2 KMP算法 一、KMP算法由来由D.E.Knuth,J.H.Morris和V.R.Pratt提出,因此称为KMP算法。是基于朴素模式匹配算法的一个优化。
二、朴素模式匹配算法一旦发现当前这个子串中某个字符不匹配,就只能转而匹配下一个子串(从下一个子串的头部开始)。
三、朴素模式匹配算法优化思路(1)模式串第6个元素不匹配。主指针i不变,模式串指针j=3。
(2)模式串第5个元素不匹配。主指针i不变,模式串指针j=2。
(3)模式串第4个元素不匹配。主指针i不变,模式串指针j=2。
(4)模式串第3个元素不匹配。主指针i不变,模式串指针j=1。
(5)模式串第2个元素不匹配。主指针i不变,模式串指针j=1。
(6)模式串第1个元素不匹配。主指针i=i+1,模式串指针j=1。
经过优化后,主串指针不再“回溯”。
四、如何用代码实现KMP匹配过程
使用 next 数组,next[ i ] 存放当第 i 个元素匹配失败时,模式串指针应该指向的位置,需要针对 next[ 1 ] 单独判断。
next 数组只和相对较短的模式串有关,和长长的主串无关。
KMP 算法就是先根据模式串T,求出 next 数组,再利用 next 数组进行匹配(主串指针不回溯)。
// KMP_对比
int Index_KMP(SString S, SString T, int next[]){
// i主串指针,j子串指针
int i = 1, j = 1; // 初始都指向各自的第一个字符位置
while(i <= S.length && j <= T.length){
// 如果当前属于子串第一个字符就不匹配的情况(j==0),或者当前比较字符相同
if(j == 0 || S.ch[i] == T.ch[j]){
++i; // 各自向后移一位,等待下一次的比较
++j;
}else{
j = next[j]; // 根据 next 数组进行子串指针j的右移
}
}
if(j > T.length){
return i-T.length; // 匹配成功
}else{
return 0;
}
}
时间复杂度分析:
最坏时间复杂度O(m+n)
求 next 数组时间复杂度 O(m),
模式匹配过程最坏时间复杂度 O(n)。
4.2_3 KMP算法_求 next 数组 一、手算next 数组的作用:当模式串的第 j 个字符失配时,从模式串的第 next[j] 个字符开始继续向后匹配。
(1)任何模式串都一样,第 1 个字符不匹配时,只能匹配下一个字符串,因此 next[ 1 ] 可以直接写 0;
(2)任何模式串都一样,第 2 个字符不匹配时,只能匹配下一个字符串,因此 next[ 2 ] 可以直接写 1;
(3)在不匹配位置的前面为分割线,模式串一步步右移,直到“对上”主串之前,j 指针指向第几个,next[ i ] 的值就是几;
二、KMP算法(next数组)完整代码运行结果:
完整代码:
// 20220605 KMP算法(next数组写法)
#include
#include
using namespace std;
// 串的静态数组存储
#define MAXLEN 255
typedef struct{
char ch[MAXLEN]; // 字符存储在数组里,从下标1开始存
int length; // 串的长度
}SString;
// next数组全局变量声明
int next[MAXLEN];
// next数组(传入模式串、空next数组)
void get_next(SString T, int next[]){
int i = 1, j = 0;
next[1] = 0; // 针对第一个字符做特殊判断
while(i < T.length){
if(j == 0 || T.ch[i] == T.ch[j]){
++i;
++j;
next[i] = j;
}else{
j = next[j];
}
}
}
// KMP对比(传入主串、模式串、next数组)
int Index_KMP(SString S, SString T, int next[]){
int i = 1, j = 1; // 指向主串、模式串的两个指针
while(i <= S.length && j <= T.length){
// 第一个字符就不匹配的情况j==0
// 或 匹配成功的情况
if(j == 0 || S.ch[i] == T.ch[j]){
++i; // 各自指针后移一位,等待下一位的比较
++j;
}else{
j = next[j]; // 根据next数组把子串右移
}
}
if(j > T.length){
return i - T.length; // 匹配成功
}else{
return 0;
}
}
// 串的赋值
bool StrAssign(SString &T, string chars){
T.length = chars.length(); // 长度
for(int i = 1; i <= T.length; i++){
T.ch[i] = chars[i-1];
}
return true;
}
// 串的打印
bool Printch(SString S){
for(int i = 1; i <= S.length; i++){
cout << S.ch[i];
}
}
int main(){
// 主串、模式串的声明
SString S, T;
// 主串、子串的输入、赋值
StrAssign(S, "Do you like Madoka?");
StrAssign(T, "Madoka");
// 测试输出
cout << "主串值为:";
Printch(S);
cout << endl;
cout << "子串值为:";
Printch(T);
cout << endl;
// next 数组
cout << "子串的next数组为:" << endl;
get_next(T, next);
for(int i = 1; i <= T.length; i++){
cout << next[i] << " ";
}
cout << endl;
// KMP匹配
cout << "KMP寻找到的子串在主串的位置为:" << Index_KMP(S, T, next) << endl;
return 0;
}
4.2_4 KMP算法的进一步优化
一、next 数组的优化思路
若初次匹配 S[ 3 ] 与 T[ 3 ] 不匹配,那么 S[ 3 ] 一定不为 'a' ,优化思路是把 next[ 3 ] 直接存 0,跳过 next[ 3 ]。
同样的情况,当模式串 next[ 5 ] 不匹配时,表示对应的主串 S[ ? ] 也一定不为 'b',优化思路是把 next[ 5 ] 直接存 1,将主串的该字符与模式串T中的第一个字符开始比较。
优化过后的 next 数组为:
Q:什么样的模式串可以用到 next 数组优化?
A:找到当前匹配失败的字符对应的 next[ x ] 数组存放的数字 y,如果模式串 T 的第 y 号字符与 第 x 号字符相等,就把next[ x ] 数组存放的数字 y 优化成 next[ y ] 数组存放的数字。
练习1:
练习2:
二、KMP算法(nextval数组)完整代码运行结果如下:
完整代码:
// 20220605 KMP(nextval)
#include
#include
using namespace std;
// 串的静态数组存储
#define MAXLEN 255
typedef struct{
char ch[MAXLEN]; // 字符存储(从下标1开始)
int length; // 长度
}SString;
// next、nextval数组
int next[MAXLEN];
int nextval[MAXLEN];
// next数组
void get_next(SString T, int next[]){
int i = 1, j = 0;
next[1] = 0;
while(i < T.length){
if(j == 0 || T.ch[i] == T.ch[j]){
++i;
++j;
next[i] = j;
}else{
j = next[j];
}
}
}
// nextval数组(这里用的是从next数组推出来nextval的写法)
void get_nextval(SString T, int next[]){
for(int j = 2; j <= T.length; j++){ // 从第二个字符开始遍历
if(T.ch[next[j]] == T.ch[j]){ // 判断 当前next[j]存放的数值所对应的字符 与 本身对应的第j个字符是否相等
nextval[j] = nextval[next[j]]; // 当前nextval[j]更新成前面与本字符相等的nextval数组的值
}else{
nextval[j] = next[j];
}
}
}
// KMP对比
int Index_KMP(SString S, SString T, int next[]){
int i = 1, j = 1; // 指向主串,模式串的指针,初始指向各自的第一个字符
while(i <= S.length && j <= T.length){
if(j == 0 || S.ch[i] == T.ch[j]){ // 初次匹配或匹配成功
++i; // 两个指针都后移
++j;
}else{
j = next[j]; // 右移模式串
}
}
if(j > T.length){
return i - T.length; // 匹配成功
}else{
return 0;
}
}
// 串的赋值
bool StrAssign(SString &T, string chars){
T.length = chars.length(); // 长度
for(int i = 1; i <= T.length; i++){ // 字符
T.ch[i] = chars[i-1];
}
return true;
}
// 串的打印
bool Printch(SString S){
for(int i = 1; i <= S.length; i++){
cout << S.ch[i];
}
}
int main(){
// 主串、模式串的声明
SString S, T;
// 初始化
StrAssign(S, "Do you like ababaa Madoka?");
StrAssign(T, "ababaa");
// 输出测试
cout << "主串值为:";
Printch(S);
cout << endl << "子串值为:";
Printch(T);
cout << endl;
// nextval数组
cout << "nextval数组为:" << endl;
get_next(T, next);
get_nextval(T, next);
for(int i = 1; i <= T.length; i++){
cout << nextval[i] << " ";
}
cout << endl;
// KMP匹配结果
cout << "模式串在主串中第一次出现的位置为:" << Index_KMP(S, T, nextval);
return 0;
}
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)