zoukankan      html  css  js  c++  java
  • 字符串匹配算法

    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

  • 相关阅读:
    POJ 2723 Get Luffy Out(2-SAT)
    ZOJ 3613 Wormhole Transport
    HDU 4085 Peach Blossom Spring
    NBUT 1221 Intermediary
    NBUT 1223 Friends number
    NBUT 1220 SPY
    NBUT 1218 You are my brother
    PAT 1131. Subway Map (30)
    ZSTU OJ 4273 玩具
    ZSTU OJ 4272 最佳淘汰算法
  • 原文地址:https://www.cnblogs.com/LyningCoder/p/3945984.html
Copyright © 2011-2022 走看看