zoukankan      html  css  js  c++  java
  • 第4章学习小结_串(BF&KMP算法)、数组(三元组)

      这一章学习之后,我想对串这个部分写一下我的总结体会。

      串也有顺序和链式两种存储结构,但大多采用顺序存储结构比较方便。字符串定义可以用字符数组比如:char c[10];也可以用C++中定义一个字符串string a;这就需要根据具体场景来选择合适方便操作的方法。还有空串和空格串是不同的,空串字符长度为0(符号‘,空格串包含一个或多个空格。这一章学习了两个串的模式匹配算法,特别是KMP算法,从中受益匪浅。

    一、串

    1、BF(Brute-Force)算法

      这个模式匹配算法简单直观,被人们称为暴力算法。它就是将模式串跟主串从开头一个一个比较,如果匹配失败,又从模式串第二个字符一次比较,以此类推,逻辑思维不高,所以简单,容易理解,现在用途还很大。

           i 0 1 2 3 4
    主串 a b c a b
    模式串j(第1次比较) c a b    
              (第2次比较)   c a b  
              (第3次比较)     c a b

    这里我是用字符串下标,从0开始,可以看出每次比较模式串都从j=0开始,而i就是上次初始比较的下一个就是i=i-j+1,比如上面的第一次比较后i=0,j=0;下一次i=0-0+1,即从i=1进行匹配。

    现在以题目为例,写一下我的解题过程。

    7-1 串的模式匹配 

      给定一个主串S(长度<=10^6)和一个模式T(长度<=10^5),要求在主串S中找出与模式T相匹配的子串,返回相匹配的子串中的第一个字符在主串S中出现的位置。

      输入格式:  输入有两行: 第一行是主串S; 第二行是模式T.

      输出格式:     输出相匹配的子串中的第一个字符在主串S中出现的位置。若匹配失败,输出0.

    输入样例:

    在这里给出一组输入。例如:

    aaaaaba
    ba
    

    输出样例:

    在这里给出相应的输出。例如:6

    int BF(string A,string B)//这里算法是从下标为0开始,所以跟书里有点不同
    {
        int i=0,j=0;
        while(i<A.length()&&j<B.length())
        {
            if(A[i]==B[j])
            {
                ++i;++j;
            }
            else 
            {
                i=i-j+1;j=0;//根据上面分析,下标后退进行比较
            }
        }
        if(j>=B.length()) return i-j+1; //返回匹配成功第一个位置
        return 0;
    }
    View Code

    我感觉用下标比较容易操作,只需要改一下相应的位置,然后直接用C++定义字符串string,再输入字符串,相对也比较简洁。

    主函数如下:

    int main()
    {
        string a,b;
        getline(cin,a);//输入一个串之后回车就可以输入下一个串
        getline(cin,b);
        //int num=BF(a,b);
            //int num=KMP(a,b);这里两个选一个,下面KMP算法的主函数一样
        cout<<num;
        return 0;
     } 
    View Code

    这道题用BF算法可以简单就解决了,那为啥还要有KMP算法呢?

      一般情况下,BF算法时间复杂度为O(m*n),数据量不大时候,执行时间近似为O(m+n),但如果是庞大数据,那它的效率就很低了,我们老师专门设了一个测试点主串长度100万,模式长度10万,而且是那种主串为aaaaaaaa...,模式串是ba......。所以,从第一个字符比较一直不匹配,而模式串一直回溯到j=0,这样工作量超大,所以上面BF算法提交之后在这个测试点是运行超时的。

    这样,才有优化算法KMP,第一次看的时候一脸懵,无法理解三个大牛的逻辑思维。所以我搜索了几篇博客认真学习,试图融入大牛的思维世界。

    (链接:https://blog.csdn.net/henuyx/article/details/44653799https://www.cnblogs.com/tangzhengyue/p/4315393.html

    2、KMP算法

    这种改进算法是由D.E.Knuth、J.H.Morris、V.R.Pratt三位大牛同时设计实现的。下面我就捋一下我入门的思路方法。

      KMP最让人难以理解就是它的next数组是如何得到的,开始我觉得很神奇,next数组居然实现了字符匹配的“跳转”。举个例子,看看是如何算出next数组的。

    模式串  a b a a b c a c


    下标 j  0 1 2 3 4 5 6 7


    next[j] -1 0 0 1 1 2 0 1

    先算一下每个字符的最长相等前后缀吧

      前缀 后缀 最长相等前后缀
    a 0
    ab a b 0
    aba a,ab a,ba 1
    abaa a,ab,aba a,aa,baa 1
    abaab a,ab,aba,abaa b,ab,aab,baab 2
    abaabc a,ab,aba,abaa,abaab c,bc,abc,aabc,baabc, 0
    abaabca a,ab,aba,abaa.abaab,abaabc a,ca,bca,abca,aabca,baabca 1
    abaabcac a,ab,aba,abaa,abaab,abaabc,abaabca c,ac,cac,bcac,abcac,aabcac,baabcac 0

    经过观察,每个字符对应next数组的值为其前面的字符的最长相等前后缀,然后因为时按下标比较,将next[0]=-1;所以现在给出任意一个模式串,都可以很快确定它的next数组值。如:a b c d a b d, next[j]={-1,0,0,0,0,1,2}。现在就上计算next值的算法:

    int* getnext(string T)  //定义指针函数getnext(),求模式串T的next函数值并放入next数组
    {
        int* next = new int[T.length()];    //动态申请模式串T存储空间 
        int Tl = T.length();
        int i = 0;    // T的下标
        int j = -1;   //因为是按下标比较,所以 j = -1,next[0] = -1;
        next[0] = -1;
        while(i<Tl)//T串未比较到串尾 
        {
            if(j==-1||T[i]==T[j])//若字符匹配,或者j==-1,则进行下一个字符比较,并求出T[i]字符对应的next值
            {                    //T[i]是后缀,T[j]是前缀 
                ++i;++j;next[i]=j;
            }
            else j=next[j];//若字符不匹配,求出字符对应的next值
        }
        return next;//因为是指针函数 ,所以返回next数组首地址 
    
    }
    View Code

    刚开始,根据书里的代码实现不了将next传到KMP函数体里面,查了相关资料,可以通过指针函数将数组传过去,其实是返回数组首地址。

      KMP算法函数:

    int KMP(string S,string T)//KMP算法 
    {
        int i=0,j=0; 
        int sl=S.length();
        int tl=T.length();
        int *next=getnext(T);//用next指针接受指针函数传来的数组地址 
        
        while(i<sl&&j<tl)
        {
            if(j==-1||S[i]==T[j])//如果j = -1,或者当前字符匹配成功(即S[i] == P[j])
            {
                ++i;++j;
            }
            else 
            {
                j=next[j];//next[j]即为j所对应的next值 
            }
        }
        if(j>=tl) return i-j+1;//匹配成功,返回第一次匹配位置
        else return 0;//匹配失败,返回0
    }
    View Code

    这道题我从理解到完成用了差不多1天时间,大部分时间花费在理解算法上面,特别是“跳转”的next数组,感觉既神奇又有趣。而PTA的这道题经过几次debug,主要是细节错误导致开始运行结果错误。就这样,要通过最后一个测试点然后学到了KMP算法思想,还是挺好的。

    二、数组

      主要学习是特殊矩阵的压缩存储,节省空间,放入新的数组,还有稀疏矩阵,用三元组表来表示,而十字链表就比较难了。三元组定义:

    typedef struct{
        int i,j,value; //行下标i,列下标j,值value
    }node;//三元组
    
    typedef struct{
        int m,n,nzero;//矩阵行数m,矩阵列数n,非零个数nzero
        node a[501];//三元组数组
    }Matrix;//三元组表

    PTA上面用这个方法就很简单解决

    现在存在的问题就是,写代码能力不高,思路虽然比较清晰,但是再转化为代码时候需要花较多时间,上次小测也是这样,因为写代码量不够,最后做题时间不够,最后一道简单的题目没来得及做,所以,以后需要花时间读写代码,这样才能做得更好。谢谢您的阅读!

    ---------------------------------------------------------书上有路,学海无涯。 生活总是很忙碌,也许这才是生活真正的奥秘。--------------------------------------------------------- 作者:Charzueus 来源:博客园 本博文版权归作者所有! 禁止商业转载等用途或联系作者授权,非商业转载请注明出处! 版权声明:本文为博主原创文章,转载请附上原文出处链接和本声明。
  • 相关阅读:
    Linux找回root密码
    关于Linux的随笔笔记
    需求征集系统进度03
    需求征集系统进度02
    需求征集系统进度01
    第六周总结
    阅读笔记03
    第一周总结
    第五周总结
    阅读笔记02
  • 原文地址:https://www.cnblogs.com/chenzhenhong/p/10691320.html
Copyright © 2011-2022 走看看