给定一个文本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. 《用Java写一个通用的服务器程序》01 综述

    最近一两年用C++写了好几个基于TCP通信类型程序,都是写一个小型的服务器,监听请求,解析自定义的协议,处理请求,返回结果.每次写新程序时都把老代码拿来,修改一下协议解析部分和业务处理部分,然后就一个 ...

  2. 微信小程序与Java后台的通信

    一.写在前面 最近接触了小程序的开发,后端选择Java,因为小程序的代码运行在腾讯的服务器上,而我们自己编写的Java代码运行在我们自己部署的服务器上,所以一开始不是很明白小程序如何与后台进行通信的, ...

  3. Shell编程基础篇

    1.1 前言 1.1.1 为什么学Shell Shell脚本语言是实现Linux/UNIX系统管理及自动化运维所必备的重要工具, Linux/UNIX系统的底层及基础应用软件的核心大都涉及Shell脚 ...

  4. ChatterBot之使用mongodb 03

    上一篇我们已经搭建好了mongodb环境,本篇为简单示例. 废话不多说先上代码然后开始讲解; !!!别忘了打开你的mongdb服务!!!,如果没有mongodb请看上篇如何安装mongodb; # - ...

  5. 【java提高】---HashMap解析(一)

    HashMap解析(一) 平时一直再用hashmap并没有稍微深入的去了解它,自己花点时间想往里面在深入一点,发现它比arraylist难理解很多,好多东西目前还不太能理解等以后自己知识更加丰富在过来 ...

  6. String、StringBuilder和StringBuffer

    1.string不可变性 java的docs有这样一句话:Strings are constant; their values cannot be changed after they are cre ...

  7. HDU-1242-Rescu

    Rescue Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Sub ...

  8. Hibernate框架进阶(上篇)

    导读 前面一片文章介绍了Hibernate框架的入门,主要是讲解Hibernate的环境搭建和简单测试,有兴趣的童鞋出门左转.本文在入门的基础上进行Hibernate的进阶讲解,分为上中下三篇,本篇为 ...

  9. 小白的Python之路 day1

    Python之路,Day1 - Python基础1 本节内容 Python介绍 发展史 Python 2 or 3? 一. Python介绍 python的创始人为吉多·范罗苏姆(Guido van ...

  10. iscroll4 input textarea不能获得焦点问题

    最近在做移动端项目的时候,使用iscroll4实现页面滚动效果,之后发现页面中的input,textarea等不能得到焦点,输入内容. 问题原因是: 使用iscroll之后,输入框无法聚焦,页面文字等 ...