给定一个文本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. php垃圾回收

    php所有的变量都存在一个zval的结构里面,通过refcount和is_ref来存储变量的引用关系.refcount是变量的引用次数,is_ref是变量是否被引用,当is_ref=0的时候refco ...

  2. 项目实战——企业级Zabbix监控实战(一)

    项目实战--企业级Zabbix监控实战 实验一:Zabbix监控的搭建 1.实验准备 centos系统服务器3台. 一台作为监控服务器, 两台台作为被监控节点, 配置好yum源. 防火墙关闭. 各节点 ...

  3. CORS(跨站资源共享)介绍

    起因 有同学在nginx站点配置中加了一行Access-Control-Allow-Origin *,导致微信中业务数据异常,抓包看http头有两个Access-Control-Allow-Origi ...

  4. memcached 缓存数据库应用实践

    1.1 数据库对比 缓存: 将数据存储到内存中,只有当磁盘胜任不了的时候,才会启用缓存   缺点:断电数据丢失(双电),用缓存存储数据的目的只是为了应付大并发的业务. 数据库: mysql(关系型数据 ...

  5. Azure 基础:使用 Traffic Manager 分流用户请求

    为了减少 web 服务器的宕机时间,同时也提高服务器的响应性能,我们往往部署多个站点并通过负载均衡来对外提供服务.Azure 提供的 Traffic Manager 服务属于负载均衡的一种,特点是工作 ...

  6. 初识分布式计算:从MapReduce到Yarn&Fuxi

      这些年,云计算.大数据的发展如火如荼,从早期的以MapReduce为代表的基于文件系统的离线数据计算,到以Spark为代表的内存计算,以及以Storm为代表的实时计算,还有图计算等等.只要数据规模 ...

  7. Postgres中表和元组的组织方式

    PG version 9.5.3 PG中四种堆文件: 普通堆 临时堆 序列堆 TOAST表 PageHeaderData长度为24(截图为8.4版本,20字节)个字节包含的内容如下: 空闲空间的起始和 ...

  8. Java读取打印机自定义纸张.

    打印出现截断? 对于自定义纸张打印, 一定要先在打印机配置那边添加, 不然会出现截断. 例如打印1000*500, 出来是正常的, 打印216*139, 出现了截断. 因为java默认的打印, 会从打 ...

  9. Scala入门系列(十一):模式匹配

    引言 模式匹配是Scala中非常有特色,非常强大的一种功能. 类似于Java中的switch case语法,但是模式匹配的功能要比它强大得多,switch只能对值进行匹配,但是Scala的模式匹配除了 ...

  10. 升级安装php7要考虑的几个方面

    php7从推出到现在,前两天官方已经发布到7.2版本,把自己升级和安装php7中遇到的问题和解决方法做一个笔记,内容主要包括:1) mysql2) php3) nginx4) memcache扩展5) ...