第四章学习小结 串的模式匹配 解题心得体会

Posted suhan717

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第四章学习小结 串的模式匹配 解题心得体会相关的知识,希望对你有一定的参考价值。

串的模式匹配 解题心得体会

关于串,模式匹配是其一个很重要的问题。针对这个问题,书上讲了两种模式匹配的算法,即BF算法和KMP算法,下面针对这两种算法的实现谈谈我的心得。

一、BF算法的探索

【错误代码1】

#include<iostream>
#include<string.h>
using namespace std;
 typedef struct{
    char ch[1000002];
    int length;
 }SString;


void  Index_BF(SString S, SString T, int pos){ 
    int i,j;
    i=pos;
    j=1;
    while ( i <= S.length && j <= T.length ) {
       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;
     }
     else{ 
     cout<<"0"<<endl;
    } 
}
}
int main()
{
    SString S,T;
    char s[1000002]={0},t[1000002]={0};
    cin>>s>>t;
    strcpy(S.ch,s);
    strcpy(T.ch,t);
    Index_BF(S, T , 1);
    return 0;
}

分析:编译轻松通过,但是怎么都不能输入进去。参考其他同学的博客,发现我的这个问题有人也遇到过,原因在于在主函数中定义了两个长度为100W的数组,程序根本跑不动。
解决:参考博客发现有两种途径解决数组过大问题:①将数组定义为全局变量 ②动态分配数组
针对我这一题的具体情况,因为我是要用到strcpy函数来copy数组的内容的,所以第一种方式显然更适合。


【错误代码2】

#include<iostream>
#include<string.h>
using namespace std;
 typedef struct{
    char ch[1000002];
    int length;
 }SString;
    char s[1000002]={0},t[1000002]={0};
    SString S,T;
    
void  Index_BF(SString S, SString T, int pos){ 
    int i,j;
    i=pos;
    j=1;
    cout<<"smooth"<<endl;
    while ( i <= S.length && j <= T.length ) {
       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;
     }
     else{ 
     cout<<"0"<<endl;
    } 
}
}
int main()
{
    cin>>s>>t;
    cout<<"good"<<endl;
    strcpy(S.ch,s);
    cout<<"OK"<<endl;
    strcpy(T.ch,t);
    cout<<"OK"<<endl;
    Index_BF(S, T , 1);
    return 0;
}

分析:这一次程序是顺利运行完了,就是没有想要的结果出来。
解决:为了查找程序在哪里断掉了,我在一些步骤之后设置了一些输出,比如cout<<"good"。此法说明我的问题出在BF算法上。经过仔仔细细的检查,发现BF算法中我打while循环的时候掉了一个括号,加上去就正常了。
接下来需要解决位置和下标的问题,我现在输出的还是下标,不是题目要求的位置。思考过后发现解决的途径有两种:改数组;改算法。这里我选择的是改算法的方法。


【正确程序1】

#include<iostream>
using namespace std;
#include<string.h>

//采用静态顺序存储结构(定长) 
typedef struct{
    char ch[1000001];   //存储串的一维数组 
    int length;     //串的长度 
}SString;
 
 SString S,T; 
 
 char s[1000001];
 char t[1000001];
 
//BF算法
//查找 模式T 在 主串S 中第pos个字符开始第一次出现的位置,并返回 
//若不存在,则返回0 (T非空,1<=pos<=S.length) 
int Index_BF(SString S,SString T,int pos)  
{               
    int i,j;
    i=pos;
    j=0;
    while(i<=S.length-1 && j<=T.length-1)
    {
        if(S.ch[i]==T.ch[j]){   //从各自的第一位开始比较,如果相同,比较下一位 
            ++i;
            ++j;
        }
        else {//如果不同,主串指针回到 上次开始比较时的字符 的下一个字符,
              //模式回到第一个字符,重新开始比较 
            i=i-j+1;
            j=0;
        } 
    }
    if(j>T.length-1)        //匹配成功 
        return i-T.length+1;//主串指针位置往回退模式长度个单位,就回到了该模式在主串中第一次出现的位置 
    else            //匹配失败 
        return 0;   //返回0(顺序存储的字符串是从下标为1的数组分量开始存储的,下标为0的分量闲置不用) 
} 
 
//主函数 
int main()
{
    cin>>s>>t;
    strcpy(S.ch,s);
    strcpy(T.ch,t);
    S.length=strlen(S.ch);
    T.length=strlen(T.ch);
    cout<<Index_BF(S,T,0)<<endl;;
    return 0;
}

现在把程序放到PTA上面跑,得到了15分,唯一错误的一个点就是超时。但这个是BF算法无法解决的了,所以我决定一鼓作气把他改成KMP算法。

一、KMP算法的探索

在真正着手写代码前,我花了很久阅读课本,也想了很久,终于弄明白了这个算法的核心思想。以下是我的思路:

①BF究竟是哪里麻烦了?


设想一种情况:主串是abcab... 子串是abcac... 如果按照BF算法我们知道,一旦b和c不匹配了,接下来比较的就是主串的第二个字符b和子串的第一个字符a,不匹配时主串和子串都右移。直到子串的首位字符a与主串的a对齐之前,我们做的都是无用功。这就是从一个具体的例子看出来的麻烦之处。


②如果说KMP可以跳过这些“无用功”,那什么情况下可以跳?这是一个特殊情况,真的能上升到一个算法的高度吗?


为了解决我以上的疑虑,我决定用抽象的模型进行说明。


设主串Si(大家不要把它看成硅元素的元素符号啦)与模式tj失配……这句话暗含(S1S2……Si-1与t1t2……tj-1是匹配的)
这里我们将主串Si固定死,让他与模式中的tk匹配(因为不知道,所以设k这个未知数,也就是说这时候模式要从j跳到k)即:

Si-k+1...Si-1 =t1...tk-1


我们由第一步的匹配就已经得到:

Si-k+1...Si-1 = tj-k+1...tj-1


把这两个式子联立,得到:
t1...tk-1 = tj-k+1...tj-1

③求k


以上我们得到了一个很重要的关系式,即t1...tk-1 = tj-k+1...tj-1,它隐含了我们想要知道的k,也就是子串该跳到哪一个地方这一信息。


观察式子,我们首先考虑一些边边角角的问题,比如k=1,j=1这些情况。


1)k=1 这时式子变成了t1...t0 = tj...tj-1 这种情况说明子串不符合要求,没有那个相等的部分。顺便也解决了我们第二问中的疑惑,其实KMP算法的优化是有条件的,要求子串与主串匹配的那部分有“相等的部分”。
这时候的处理办法也只有老老实实跳到t1与Si比较。模式啊模式!你若是堕落,KMP也救不了你!

2)j=1 其实就是模式的第一位就与主串Si不匹配。那还犹豫啥?直接把模式向右移一个呗。

④怎么用算法实现?

其实整个大框架在BF算法的基础上来说不用怎么改,只需要把回溯的那块儿稍微修改一下,即改成主串i不回溯,模式跳到k位置。

但是现在说来说去我们还是只有一串关系式,还有两个边界的情况,k到底怎么表示?对解决这个问题还是一脸懵逼。

阅读课本发现他使用一个next[j]函数来表示子串下一个回溯的位置。 其实想一想next[j]仅与模式有关,和主串半毛钱关系也没有。从已知的关系式t1...tk-1 = tj-k+1...tj-1入手,此时next[j] = k。我们求一求next[j+1]?这时候产生两种情况需要讨论:

1)若tk = tj 那么t1...tk = tj-k+1...tj 也就是next[j+1] = k+1 , 亦即next[j+1] = next[j] +1

2)若tk!=tj 是不是有一种似曾相识的感觉?对了,这又是一次模式匹配,只不过此时的模式既充当了主串又充当模式。所以我们要把模式中的第next[j]个字符和主串中的第j个字符对齐进行比较。这种情况可以利用函数的递归调用,让j=next[j]。


【正确程序2】

#include<iostream>
using namespace std;
#include<string.h>

//采用静态顺序存储结构(定长) 
typedef struct{
    char ch[1000002];   //存储串的一维数组 
    int length;     //串的长度 
}SString;
 
 SString S,T; 
 
 char s[1000002];
 char t[1000002];
 int nex[1000002];
 
//KMP算法
//查找 模式T 在 主串S 中第pos个字符开始第一次出现的位置,并返回 
//若不存在,则返回0 (T非空,1<=pos<=S.length) 
int Index_KMP(SString S,SString T,int next[])  
{               
    int i,j;
    i=j=0;
    while(i<=S.length-1 && j<=T.length-1)
    {
        if(j==-1||S.ch[i]==T.ch[j]){    //从各自的第一位开始比较,如果相同,比较下一位 
            ++i;
            ++j;
        }
        else {
            j=next[j];
        } 
    }
    if(j>T.length-1)        //匹配成功 
        return i-T.length+1;// 
    else            //匹配失败 
        return 0;
} 
 
 void get_next(SString T,int next[]){
    int i=0;
    next[0]=-1;
    int j=-1;
    while(i<T.length-1){
        if(j==-1||T.ch[i]==T.ch[j]){
            ++i;
            ++j;
            next[i]=j;
         }
         else {
            j=next[j];
         }
     }
 }
//主函数 
int main()
{
    cin>>s>>t;
    strcpy(S.ch,s);
    strcpy(T.ch,t);
    S.length=strlen(S.ch);
    T.length=strlen(T.ch);
    get_next(T,nex);
    cout<<Index_KMP(S,T,nex)<<endl;
    return 0;
}

下一周的计划之一是抽空做做前面几章的实践2,我感觉代码是越打越熟练的,所以要多加练习。而且上次小测成绩不是特别理想,一个很大的问题就是我有时候太依赖书本了,所以我的第二个目标是要培养自己不看书本打代码的能力,这也是对思维完备性的锻炼。

以上是关于第四章学习小结 串的模式匹配 解题心得体会的主要内容,如果未能解决你的问题,请参考以下文章

第四章心得体会

第四章学习小结

第四章学习小结

第四章心得体会

第四章学习小结

数据结构 第四章学习小结