• 字符串匹配算法


    1. 朴素算法

    朴素算法是最简单的字符串匹配算法,也是人们接触得最多的字符串匹配算法。

    2. Rabin-Karp算法

    一个时间复杂度为O((N-M+1)*M)的字符串匹配算法,即Rabin-Karp算法。Rabin-Karp算法的预处理时间是O(m), 匹配时间OO((N-M+1)*M),既然与朴素算法的匹配时间一样,而且还多了一些预处理时间,那为什么我们 还要学习这个算法呢?

    虽然Rain-Karp在最坏的情况下与朴素的世间复杂度一样,但是实际应用中往往比朴素算法快很多。而且该算法的 期望匹配时间是O(N+M)(参照《算法导论》)。

    在朴素算法中,我们需要挨个比较所有字符,才知道目标字符串中是否包含子串。那么, 是否有别的方法可以用来判断目标字符串是否包含子串呢?

    答案是肯定的,确实存在一种更快的方法。为了避免挨个字符对目标字符串和子串进行比较, 我们可以尝试一次性判断两者是否相等。因此,我们需要一个好的哈希函数(hash function)。 通过哈希函数,我们可以算出子串的哈希值,然后将它和目标字符串中的子串的哈希值进行比较。 这个新方法在速度上比暴力法有显著提升。

    Rabin-Karp算法的思想:

    1. 假设子串的长度为M,目标字符串的长度为N
    2. 计算子串的hash值
    3. 计算目标字符串中每个长度为M的子串的hash值(共需要计算N-M+1次)
    4. 比较hash值
    5. 如果hash值不同,字符串必然不匹配,如果hash值相同,还需要使用朴素算法再次判断

    为了快速的计算出目标字符串中每一个子串的hash值,Rabin-Karp算法并不是对目标字符串的 每一个长度为M的子串都重新计算hash值,而是在前几个字串的基础之上, 计算下一个子串的 hash值,这就加快了hash之的计算速度,将朴素算法中的内循环的世间复杂度从O(M)将到了O(1)。

    关于hash函数的详细内容,可以参考这里或者《算法导论》。

    #include<stdio.h>
    #include<string.h>
    
    // d is the number of characters in input alphabet
    #define d 256 
    
    /*  pat  -> pattern
        txt  -> text
        q    -> A prime number
    */
    void search(char *pat, char *txt, int q)
    {
        int M = strlen(pat);
        int N = strlen(txt);
        int i, j;
        int p = 0;  // hash value for pattern
        int t = 0; // hash value for txt
        int h = 1;
    
        // The value of h would be "pow(d, M-1)%q"
        for (i = 0; i < M-1; i++)
            h = (h*d)%q;
    
        // Calculate the hash value of pattern and first window of text
        for (i = 0; i < M; i++)
        {
            p = (d*p + pat[i])%q;
            t = (d*t + txt[i])%q;
        }
    
        // Slide the pattern over text one by one 
        for (i = 0; i <= N - M; i++)
        {
    
            // Chaeck the hash values of current window of text and pattern
            // If the hash values match then only check for characters on by one
            if ( p == t )
            {
                /* Check for characters one by one */
                for (j = 0; j < M; j++)
                {
                    if (txt[i+j] != pat[j])
                        break;
                }
                if (j == M)  // if p == t and pat[0...M-1] = txt[i, i+1, ...i+M-1]
                {
                    printf("Pattern found at index %d 
    ", i);
                }
            }
    
            // Calulate hash value for next window of text: Remove leading digit, 
            // add trailing digit           
            if ( i < N-M )
            {
                t = (d*(t - txt[i]*h) + txt[i+M])%q;
    
                // We might get negative value of t, converting it to positive
                if(t < 0) 
                  t = (t + q); 
            }
        }
    }
    
    /* Driver program to test above function */
    int main()
    {
        char *txt = "GEEKS FOR GEEKS";
        char *pat = "GEEK";
        int q = 101;  // A prime number
        search(pat, txt, q);
        getchar();
        return 0;
    }
    

      

    3. KMP算法

     KMP算法之所以叫做KMP算法是因为这个算法是由三个人共同提出来的,就取三个人名字的首字母作为该算法的名字。其实KMP算法与BF算法的区别就在于KMP算法巧妙的消除了指针i的回溯问题,只需确定下次匹配j的位置即可,使得问题的复杂度由O(mn)下降到O(m+n)。

      在KMP算法中,为了确定在匹配不成功时,下次匹配时j的位置,引入了next[]数组,next[j]的值表示P[0...j-1]中最长后缀的长度等于相同字符序列的前缀。

      对于next[]数组的定义如下:

     1) next[j] = -1  j = 0

     2) next[j] = max(k): 0<k<j   P[0...k-1]=P[j-k,j-1]

     3) next[j] = 0  其他

     如:

     P      a    b   a    b   a

     j      0    1   2    3   4

     next    -1   0   0    1   2

     即next[j]=k>0时,表示P[0...k-1]=P[j-k,j-1]

     因此KMP算法的思想就是:在匹配过程称,若发生不匹配的情况,如果next[j]>=0,则目标串的指针i不变,将模式串的指针j移动到next[j]的位置继续进行匹配;若next[j]=-1,则将i右移1位,并将j置0,继续进行比较。

    #include<stdio.h>
    #include<string.h>
    void getNext(char *p, int *next);
    
    int KMPMatch(char *s ,char *p)
    {
        int next[100] = {0};
        int M = strlen(s);
        int N = strlen(p);
        getNext(p,next);
        int i = 0, j = 0;
        while( i < M ) 
        {
            if(next[j] == -1 || s[i] == p[j])
            {
                i++;
                j++;
            }else
            {
                j = next[j];
            }
            if(j == N)
            {
                return i - N;
            }
        }
        return -1;
    
    }
    
    void getNext(char *p , int *next)
    {
        int j , k ;
        next[0] = -1;
        j = 0;
        k = -1;
        while(j < strlen(p))
        {
            if(k == -1 || p[j] == p[k])
            {
                k++;
                j++;
                next[j] = k;
            }else{
                k = next[k];
            }
        }
    }
    
    int main()
    {
        char *s = "lovely puppy , jianghaha";
        char *p = "jiang";
    
        printf( "匹配位置:%d
    " , KMPMatch(s , p)) ;
        return 0;
    }

    因此KMP算法的关键在于求算next[]数组的值,即求算模式串每个位置处的最长后缀与前缀相同的长度, 而求算next[]数组的值有两种思路,第一种思路是用递推的思想去求算,还有一种就是直接去求解。 

    1.按照递推的思想:

       根据定义next[0]=-1,假设next[j]=k, 即P[0...k-1]==P[j-k,j-1]

       1)若P[j]==P[k],则有P[0..k]==P[j-k,j],很显然,next[j+1]=next[j]+1=k+1;

       2)若P[j]!=P[k],则可以把其看做模式匹配的问题,即匹配失败的时候,k值如何移动,显然k=next[k]。

    4. Boyer-Moore算法

    待补充。http://blog.jobbole.com/52830/

    5. Sunday算法

    http://blog.163.com/yangfan876@126/blog/static/80612456201342205056344

  • 相关阅读:
    赏月斋源码共享计划 第三期
    概率校准与Brier分数
    模型评估——定量分析预测的质量
    Sklearn实现逻辑回归
    赏月斋源码共享计划 第二期
    技术路线范文
    开题报告范文
    赏月斋源码共享计划 第一期
    关于研究课题中的技术路线与实施方案
    Git项目的初始化
  • 原文地址:https://www.cnblogs.com/LyningCoder/p/3945984.html
Copyright © 2020-2023  润新知