zoukankan      html  css  js  c++  java
  • 搞定单模式匹配(简单,KMP)

      模式匹配是查找的一种,分为单模式匹配和多模式匹配。查找,就是在一个集合中查找一个或多个元素,查找一个元素就叫单模式匹配,查找多个元素就是多模式匹配,这里只探讨单模式匹配。虽然模式匹配看上去与数字的查找不一样,但是本质上任然是一种查找,比如在“aabaabaabaac”中查找“aabaac”,对计算机来说,处理的仍然是在集合{aabaab, abaaba, baabaa, aabaab, abaaba, baabaa, aabaac}中查找“aabaaac”,这是计算机的流处理特性决定的。所谓的简单模式匹配算法,就是顺序查找,代码如下:

    #include<stdio.h>
    #include<string.h>
    int searchstr(char S[],char T[])
    {
    int i=1,j=1;
        while(i<=strlen(S) && j<=strlen(T))// i 或 j 越界跳出 
        {
            if(S[i-1]==T[j-1])    {++i,++j;}//同则后移
            else        {i=i-j+2;j=1;}//异则结束本次比较,准备下一次比较 
        }
    if(j>strlen(T))    return i-j+1;//判断跳出类型, i 还是 j 越界跳出 
    else                return 0;    
    }
    main()
    {
    char S[]="aabaabaabaac";
    char T[]="aabaac";
    int temp=searchstr(S,T);
    if(temp)    printf("the position is : %d\n",temp);
    else        printf("ERROR!\n");
    }

      KMP算法并没有盲目搜索,而是做了一些逻辑推理跳过了一些不必要的搜索,下面就演示一下KMP是如何推理的,我们以在“aabaabaabaac”中查找“aabaac”为例,假设对比到第六位发现不匹配,这时前面5位都是匹配的。

      那么我们结束这次对比,进行下一次对比,如下图:

      “aabaa?”匹配失败,我们跳过了“abaaa??”与“baa???”这两次对比直接来到了“aa????”,为什么会这样,简单推理一下就知道了,模版是“aabaac”,很显然“abaaa??”与“baa???”都不可能匹配,只有“aa????”才【有可能】与模版匹配。事实上这个可能匹配的字符串是有一定特征的,匹配失败时我们得到aabaa这个相同的前缀,aabaa有若干前缀和若干后缀(规定缀不能为字符串本身,aabaa不是aabaa的缀),前后缀相同的有a和aa,我们称之为同缀,我们只找最长同缀aa,它的长度对我们很重要。接下来回到“aa????”这次对比:

      “aa????”和模版“aabaac”对比,最长同缀aa根本不用比,直接从红色的部分开始对比,因为i本来就是指在这里的,所以i看上去是不动的,只用重定位j就行了,这个j的新位置就是书上说的next数组,next[j-1]表示第j位对比失败后,j跳转的位置。在本例中第6位对比失败,j跳转到3,即next[5]中存储的是3。3是如何推理出来的?其实就是【1+最长同缀的长度】。现在假设是“aab?”第四位匹配失败,那么相同前缀是aab,aab没有最长同缀,所以它的最长同缀长度是0,按照规律j重定位到1+0=1位,即新一次对比的第一位,这就是为什么要加1的原因。当第一位匹配失败时,next数组不起任何作用,但我们还是象征性的把next[0]设为0,它表示第一位对比失败,当检测到j为0,我们需要把i和j都后移一位,这样j就刚好到1了,于是任何模版的next[0]都为0。任意字符X的最长同缀是0,于是任何模版的next[1]都为1。

      为什么KMP这么不好理解,原因在于书本上没有说清楚两次逻辑推理过程,第一次推理是直接跳过若干次比较来到新一次的比较的第一位,第二次推理是在新一次的对比中,又跳过了前面若干位的比较,最终定位到正确的位置。这个过程中i并没有变,而j按照某种规律重新定位了,这个规律就是【j=1+最长同缀的长度】。毫无疑问,next数组是根据模版推理出来的,所以需要事先进行计算,我们假设next已经求解,KMP代码如下(其代码与简单匹配的代码高度类似):

    int KMP(char S[],char T[],int next[],int pos)
    {
    int i=pos,j=1;//添加功能,从 S 中的pos位置开始查找 
        while(i<=strlen(S) && j<=strlen(T))// i 或 j 越界跳出 
        {
            if(j==0 || S[i-1]==T[j-1])    {++i,++j;}//第一位对比失败和任意位对比成功都处理成i,j同时后移 
            else        {j=next[j-1];}//按照某种规律重定位 j  
        }
    if(j>strlen(T))    return i-j+1;//判断跳出类型, i 还是 j 越界跳出 
    else                return 0;    
    }

      KMP函数写出来之后,不要以为就万事大吉了,离实现KMP算法还远着呢,整个KMP算法的精髓就在于推理next数组,如何推理next数组?这个时候我们就要有分治思想了。

     

      看看我们以上在做什么?我们在枚举7长度字符串可能的next值,它的取值可能是next[6]+1到0。但是它看上去就像是在“aba?”中查找“abaa”,注意此时“abaa”的next数组已经求解出来了,当第四位匹配出错,我们跳过了:

      直接来到了:

      其实我们还可以再跳过几个字符:

      这个过程是不是似曾相识呢?没错,这就是KMP的重定位j,因为abaa的next已经求解,所以我们确实是可以使用next数组的。我们注意到在匹配成功之前,i始终是不动的,i的含义其实就是“正在求解i长度字符串的next[i]”。而匹配成功时j的含义就是“i长度字符串的最长同缀”。此时j+1就是next[i]值,然后我们把i,j同时后移处理i+1长度字符串最长同缀。j=0,没有啥用,它仅仅做为一个标志,标志找不到最长同缀,即最长同缀为0。根据这些我们就可以完成getnext代码,代码如下:

    void getnext(char T[],int next[])
    {
        next[0]=0;
        next[1]=1;//没必要求解,恒成立 
        int i=2,j=1;//直接从求解2长度字符串开始 
        while(i<=strlen(T))
        {
            if(j==0||T[i-1]==T[j-1])//字符相等标志找到最长同缀,j 为 0 标志最长同缀为 0    
            {
            next[i]=j+1;// i 的含义是 i 长度字符串,j的含义是最长同缀为 j 
            ++i;++j;//i,j整体后移,表示已经求解i长度字符串,下一次将处理i+1长度字符串 
            }
            else j=next[j-1];//j重定位 
        }
    }

      充分理解了这段代码之后,我们还可以把它做的更简洁:

    void getnext(char T[],int next[])
    {
        next[0]=0;
        next[1]=1;
        int i=2,j=1;
        while(i<=strlen(T))
        {
            if(j==0||T[i-1]==T[j-1])    next[i++]=++j;//使用跟新前的 i 值和跟新后的 j 值 
            else                         j=next[j-1];
        }
    }

      全部KMP代码如下:

    #include<stdio.h>
    #include<string.h> 
    #define MAXSIZE 100 
    int KMP(char S[],char T[],int next[],int pos)
    {
    int i=pos,j=1;//添加功能,从 S 中的pos位置开始查找 
        while(i<=strlen(S) && j<=strlen(T))// i 或 j 越界跳出 
        {
            if(j==0 || S[i-1]==T[j-1])    {++i,++j;}//第一位对比失败和任意位对比成功都处理成i,j同时后移 
            else        {j=next[j-1];}//按照某种规律重定位 j  
        }
    if(j>strlen(T))    return i-j+1;//判断跳出类型, i 还是 j 越界跳出 
    else                return 0;    
    }
    
    void getnext(char T[],int next[])
    {
        next[0]=0;
        next[1]=1;
        int i=2,j=1;
        while(i<=strlen(T))
        {
            if(j==0||T[i-1]==T[j-1])    next[i++]=++j;//使用跟新前的 i 值和跟新后的 j 值 
            else                         j=next[j-1];
        }
    }
    main()
    {
        char S[]="vfyabaababm";
        char T[]="abaababm";
        //计算 next 数组 
        int next[MAXSIZE];
        getnext(T,next);
        //显示 next 数组 
        printf("the next array is : ");
        for(int i=0;i<strlen(T);i++)
        printf("%d ",next[i]);
        printf("\n");
        //显示查找结果 
    int temp=KMP(S,T,next,1);
    if(temp)    printf("the position is : %d\n",temp);
    else        printf("ERROR!\n");
    } 

     

     

     

     

  • 相关阅读:
    大约PCA算法学习总结
    内部硬盘的硬件结构和工作原理进行了详细解释
    DWZ使用注意事项
    cocos2d-x 在XML分析和数据存储
    HTML精确定位:scrollLeft,scrollWidth,clientWidth,offsetWidth完全详细的说明
    hdu 1114 Piggy-Bank
    getResources()方法
    人机博弈-吃跳棋游戏(三)代移动
    Oracle 11g client安装和配置。
    的微信公众号开发 图灵机器人接口允许调用自己的微通道成为一个智能机器人
  • 原文地址:https://www.cnblogs.com/huaxiaforming/p/4478160.html
Copyright © 2011-2022 走看看