给定一个文本txt [0..n-1]和一个模式pat [0..m-1],写一个搜索函数search(char pat [],char txt []),在txt中打印所有出现的pat [] []。可以假设n> m

例子:

Input:  txt[] = "THIS IS A TEST TEXT"
        pat[] = "TEST"
Output: Pattern found at index 10

Input:  txt[] =  "AABAACAADAABAABA"
        pat[] =  "AABA"
Output: Pattern found at index 0
        Pattern found at index 9
        Pattern found at index 12

模式搜索是计算机科学中的一个重要问题。当我们在记事本/ word文件或浏览器或数据库中搜索字符串时,使用模式搜索算法来显示搜索结果。

我们在前面的文章中已经讨论过Naive模式搜索算法。Naive算法的最坏情况复杂度是O(m(n-m + 1))。在最坏的情况下,KMP算法的时间复杂度是O(n)。

KMP(克努特莫里斯普拉特)模式搜索的Naive模式搜索算法并不在在这种情况下很好的工作:许多匹配字符,随后是不匹配的字符的情况下。以下是一些例子。

   txt[] = "AAAAAAAAAAAAAAAAAB"
   pat[] = "AAAAB"

   txt[] = "ABABABCABABABCABABABC"
   pat[] =  "ABABAC" (not a worst case, but a bad case for Naive)

KMP匹配算法使用该模式的退化性质(在模式中具有相同子模式出现不止一次的模式),并将最坏情况复杂度提高到O(n)。

KMP算法背后的基本思想是:每当我们检测到一个不匹配(在一些匹配之后),我们已经知道下一个窗口文本中的一些字符。我们利用这些信息避免匹配我们知道无论如何将匹配的字符。让我们考虑下面的例子来理解这一点。

Matching Overview
txt = "AAAAABAAABA"
pat = "AAAA"

我们对比txt和pat:
txt = "AAAAABAAABA"
pat = "AAAA"  初始位置
我们找到了一个和原始字符串相同的匹配。
下一步,我们比较txt中接下来的字符串和pat字符串 txt = "AAAAABAAABA" pat = "AAAA" [初始位置的下一个位置]
这就是KMP算法优于基本模式匹配算法的地方了。在第二次比较中,我们只比较了pattern的前4个'A',然后我们决定当前是否匹配,我们已经知道前三个匹配,我们直接跳过前三个即可。

需要预处理吗? 

上述解释产生了一个重要问题,如何知道要跳过多少字符。要知道为此,我们预处理模式并准备一个整数数组。告诉我们要跳过的字符数。

预处理概述:

  • KMP算法对pat []进行预处理,并构造一个大小为m(与模式大小相同)的辅助lps [],用于匹配时跳过字符。
  • 名称lps表示最长的正确前缀,也是后缀。。一个适当的前缀是前缀与整个字符串容许。例如,“ABC”的前缀是“”,“A”,“AB”和“ABC”。正确的前缀是“”,“A”和“AB”。字符串的后缀是“”,“C”,“BC”和“ABC”。
  • 对于其中i = 0到m-1的每个子模式pat [0..i],lps [i]存储最大匹配正确前缀的长度,其也是子模式pat [0..i]的后缀。
 lps[i] = the longest proper prefix of pat[..i]
              which ..i]. 

注意: lps [i]也可以定义为最长的前缀,也是正确的后缀。我们需要在一个地方使用正确的,以确保整个子字符串不被考虑。

Examples of lps[] construction:
For the pattern “AAAA”,
lps[] , , , ]

For the pattern “ABCDE”,
lps[] , , , , ]

For the pattern “AABAACAABAA”,
lps[] , , , , , , , , , , ]

For the pattern “AAACAAAAAC”,
lps[] , , , , , , , , , ] 

For the pattern “AAABAAA”,
lps[] , , , , , , ]

搜索算法:
与Naive算法不同的是,我们将模式依次匹配一个,然后比较每次匹配中中的所有字符,我们使用来自lps []的值来决定下一个要匹配的字符。这个想法是不匹配我们知道无论如何将匹配的字符。

如何使用lps []来决定下一个职位(或知道要跳过的字符数)?

      • 我们开始与j = 0的pat [j]与当前文本窗口的字符进行比较。
      • 我们保持匹配字符txt [i]和pat [j],并在p​​at [j]和txt [i]保持匹配的同时不断增加i和j 。
      • 当我们看到不匹配的时候
        • 我们知道,字符pat [0..j-1]与txt [i-j + 1 ... i-1]匹配(注意,j从0开始并且只有在匹配时才增加)。
        • 我们也知道(从上面的定义中)lps [j-1]是pat [0 ... j-1]的字符数,它们都是正确的前缀和后缀。
        • 从上面两点我们可以得出结论,我们不需要将这些lps [j-1]字符与txt [ij ... i-1]进行匹配,因为我们知道这些字符无论如何将匹配。让我们考虑上面的例子来理解这一点。
txt[] = "AAAAABAAABA"
pat[] = "AAAA"
lps[] = {0, 1, 2, 3} 

i = 0, j = 0
txt[] = "AAAAABAAABA"
pat[] = "AAAA"
txt[i] and pat[j[ match, do i++, j++

i = 1, j = 1
txt[] = "AAAAABAAABA"
pat[] = "AAAA"
txt[i] and pat[j[ match, do i++, j++

i = 2, j = 2
txt[] = "AAAAABAAABA"
pat[] = "AAAA"
pat[i] and pat[j[ match, do i++, j++

i = 3, j = 3
txt[] = "AAAAABAAABA"
pat[] = "AAAA"
txt[i] and pat[j[ match, do i++, j++

i = 4, j = 4
Since j == M, print pattern found and resset j,
j = lps[j-1] = lps[3] = 3

Here unlike Naive algorithm, we do not match first three
characters of this window. Value of lps[j-1] (in above
step) gave us index of next character to match.
i = 4, j = 3
txt[] = "AAAAABAAABA"
pat[] =  "AAAA"
txt[i] and pat[j[ match, do i++, j++

i = 5, j = 4
Since j == M, print pattern found and reset j,
j = lps[j-1] = lps[3] = 3

Again unlike Naive algorithm, we do not match first three
characters of this window. Value of lps[j-1] (in above
step) gave us index of next character to match.
i = 5, j = 3
txt[] = "AAAAABAAABA"
pat[] =   "AAAA"
txt[i] and pat[j] do NOT match and j > 0, change only j
j = lps[j-1] = lps[2] = 2

i = 5, j = 2
txt[] = "AAAAABAAABA"
pat[] =    "AAAA"
txt[i] and pat[j] do NOT match and j > 0, change only j
j = lps[j-1] = lps[1] = 1 

i = 5, j = 1
txt[] = "AAAAABAAABA"
pat[] =     "AAAA"
txt[i] and pat[j] do NOT match and j > 0, change only j
j = lps[j-1] = lps[0] = 0

i = 5, j = 0
txt[] = "AAAAABAAABA"
pat[] =      "AAAA"
txt[i] and pat[j] do NOT match and j is 0, we do i++.

i = 6, j = 0
txt[] = "AAAAABAAABA"
pat[] =       "AAAA"
txt[i] and pat[j] match, do i++ and j++

i = 7, j = 1
txt[] = "AAAAABAAABA"
pat[] =       "AAAA"
txt[i] and pat[j] match, do i++ and j++

  下面是代码实现:

// C++ program for implementation of KMP pattern searching
// algorithm
#include<bits/stdc++.h>

void computeLPSArray(char *pat, int M, int *lps);

// Prints occurrences of txt[] in pat[]
void KMPSearch(char *pat, char *txt)
{
    int M = strlen(pat);
    int N = strlen(txt);

    // create lps[] that will hold the longest prefix suffix
    // values for pattern
    int lps[M];

    // Preprocess the pattern (calculate lps[] array)
    computeLPSArray(pat, M, lps);

    ;  // index for txt[]
    ;  // index for pat[]
    while (i < N)
    {
        if (pat[j] == txt[i])
        {
            j++;
            i++;
        }

        if (j == M)
        {
            printf("Found pattern at index %d n", i-j);
            j = lps[j-];
        }

        // mismatch after j matches
        else if (i < N && pat[j] != txt[i])
        {
            // Do not match lps[0..lps[j-1]] characters,
            // they will match anyway
            )
                j = lps[j-];
            else
                i = i+;
        }
    }
}

// Fills lps[] for given patttern pat[0..M-1]
void computeLPSArray(char *pat, int M, int *lps)
{
    // length of the previous longest prefix suffix
    ;

    lps[] = ; // lps[0] is always 0

    // the loop calculates lps[i] for i = 1 to M-1
    ;
    while (i < M)
    {
        if (pat[i] == pat[len])
        {
            len++;
            lps[i] = len;
            i++;
        }
        else // (pat[i] != pat[len])
        {
            // This is tricky. Consider the example.
            // AAACAAAA and i = 7. The idea is similar
            // to search step.
            )
            {
                len = lps[len-];

                // Also, note that we do not increment
                // i here
            }
            else // if (len == 0)
            {
                lps[i] = ;
                i++;
            }
        }
    }
}

// Driver program to test above function
int main()
{
    char *txt = "ABABDABACDABABCABAB";
    char *pat = "ABABCABAB";
    KMPSearch(pat, txt);
    ;
}

输出:

Found pattern at index 

预处理算法:
在预处理部分,我们计算lps []中的值。为此,我们跟踪前一个索引的最长前缀后缀值(我们使用len变量用于此目的)的长度。我们将lps [0]和len初始化为0.如果pat [len]和pat [i]匹配,我们将len加1,并将增加的值赋给lps [i]。如果pat [i]和pat [len]不匹配,len不为0,我们将len更新为lps [len-1]。有关详细信息,请参阅下面的代码中的computeLPSArray()。

预处理的插图(或构建lps [])

pat[] = "AAACAAAA"

len = , i  = .
lps[] , we move
to i = 

len = , i  = .
Since pat[len] and pat[i] match, do len++,
store it in lps[i] and do i++.
len = , lps[] = , i = 

len = , i  = .
Since pat[len] and pat[i] match, do len++,
store it in lps[i] and do i++.
len = , lps[] = , i = 

len = , i  = .
Since pat[len] and pat[i] ,
] = lps[] = 

len = , i  = .
Since pat[len] and pat[i] ,
len = lps[len-] = lps[] = 

len = , i  = .
Since pat[len] and pat[i] ,
Set lps[] =  and i = .

len = , i  = .
Since pat[len] and pat[i] match, do len++,
store it in lps[i] and do i++.
len = , lps[] = , i = 

len = , i  = .
Since pat[len] and pat[i] match, do len++,
store it in lps[i] and do i++.
len = , lps[] = , i = 

len = , i  = .
Since pat[len] and pat[i] match, do len++,
store it in lps[i] and do i++.
len = , lps[] = , i = 

len = , i  = .
Since pat[len] and pat[i] ,
] = lps[] = 

len = , i  = .
Since pat[len] and pat[i] match, do len++,
store it in lps[i] and do i++.
len = , lps[] = , i = 

We stop here as we have constructed the whole lps[].

ok,如果有问题,随时提问

搜索模式| 系列2——KMP算法的更多相关文章

  1. 模式串匹配之KMP算法

    模式串匹配之KMP算法 KMP算法 模式值计算(next[j]) (1) next[0]=-1,  第一个字符模式值为-1 (2) next[j]=-1, T中下标为j的字符与首字符相同,且j前面的1 ...

  2. 讲不明白自杀系列:KMP算法

    算法:KMP排序 算法分析 KMP算法是一种快速的模式匹配算法.KMP是三位大师:D.E.Knuth.J.H.Morris和V.R.Pratt同时发现的,所以取首字母组成KMP. 少部分图片来自孤~影 ...

  3. KMP算法的Next数组详解

    转载请注明来源,并包含相关链接. 网上有很多讲解KMP算法的博客,我就不浪费时间再写一份了.直接推荐一个当初我入门时看的博客吧:http://www.cnblogs.com/yjiyjige/p/32 ...

  4. 【转】KMP算法

    转载请注明来源,并包含相关链接.http://www.cnblogs.com/yjiyjige/p/3263858.html 网上有很多讲解KMP算法的博客,我就不浪费时间再写一份了.直接推荐一个当初 ...

  5. KMP算法的Next数组详解 转

    这个写的很好,还有讲kmp,值得一看. http://www.cnblogs.com/tangzhengyue/p/4315393.html 转载请注明来源,并包含相关链接. 网上有很多讲解KMP算法 ...

  6. KMP算法的Next数组详解(转)

    转载请注明来源,并包含相关链接. 网上有很多讲解KMP算法的博客,我就不浪费时间再写一份了.直接推荐一个当初我入门时看的博客吧: http://www.cnblogs.com/yjiyjige/p/3 ...

  7. (转)KMP算法实现。超级赞!见过的最容易理解的

    网上有很多讲解KMP算法的博客,我就不浪费时间再写一份了.直接推荐一个当初我入门时看的博客吧:http://www.cnblogs.com/yjiyjige/p/3263858.html这位同学用详细 ...

  8. 串的模式匹配和KMP算法

    在对字符串的操作中,我们经常要用到子串的查找功能,我们称子串为模式串,模式串在主串中的查找过程我们成为模式匹配,KMP算法就是一个高效的模式匹配算法.KMP算法是蛮力算法的一种改进,下面我们先来介绍蛮 ...

  9. 模式匹配的KMP算法详解

    这种由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现的改进的模式匹配算法简称为KMP算法.大概学过信息学的都知道,是个比较难理解的算法,今天特把它搞个彻彻底底明明白白. 注意到这 ...

随机推荐

  1. OC面向对象的三大特性

    一.面向对象的三大特性:封装(成员变量).继承和多态 1. set方法和get方法 1. set方法和get方法的使用场合 @public的成员可以被随意赋值,应该使用set方法和get方法来管理成员 ...

  2. 2386:Lake Counting-poj

    总时间限制:  1000ms 内存限制:  65536kB 描述 Due to recent rains, water has pooled in various places in Farmer J ...

  3. 《Maven实战》 第7章 生命周期与插件

    7.1什么是生命周期 软件开发人员每天都在对项目进行清理.编译.测试及部署,Maven生命周期是对所有构建过程进行抽象和统一,含项目的清理.初始化.编译.测试.打包.集成测试.验证.部署和站点生成等几 ...

  4. H5+JS+JQuery+ECharts实现异步加载

    这几天,看了一下ECharts官网的API和Demo发现很有意思,于是就利用模型预测产生的数据做一个伪实时的动态数据显示 . 首先,创建一个index.html的文件,我用的vscode打开的,进行编 ...

  5. static,final,包,访问修饰符,内部类

    static 关键字可以修饰成员变量,被static修饰的成员变量归属于类static关键字可以修饰成员方法:被static修饰的成员方法可以直接使用类名调用,也可以通过对象调用,建议使用类名.非静态 ...

  6. OCPC(Optimized Cost per Click)机制

    背景 在线广告中,广告按照CPM排序,排在前面的广告竞争有限广告位(截断).其中,CPM=bid*pctr.注GSP二价计费的,按照下一位bid计费.适当调整bid,可以提高竞价的排名,从而获得展现的 ...

  7. Linux服务器病毒清理实践

    背景:客户服务器被挂载木马病毒用以挖矿(比特币). 本次清理通过Linux基本命令完成.其原理也比较简单,通过ps命令查看服务器异常进程,然后通过lsof命令定位进程访问的文件,找到异常文件删除之,最 ...

  8. centos 下安装pptp (vpn) 的方法

    废话少说     01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 3 ...

  9. linux apt-cache使用方法

    apt-cache是linux下的一个apt软件包管理工具,它可查询apt的二进制软件包缓存文件.APT包管理的大多数信息查询功能都可以由apt-cache命令实现,通过apt-cache命令配合不同 ...

  10. QT制作一个图片播放器

    前言:使用qt制作了一个简单的图片播放器,可以播放gif.png等格式图片 先来看看播放器的功能(当然是很简陋的,没有很深入的设计): 1.点击图片列表中图片进行播放. 2.自动播放,播放的图片的间隔 ...