4种字符串匹配算法:KMP(下),4种字符串匹配算法:BS朴素 Rabin-karp(上),4种字符串匹配算法:有限自动机(中)

回顾:4种字符串匹配算法:BS朴素 Rabin-karp(上)

        4种字符串匹配算法:有限自动机(中)

1、图解

  KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现,因此人们称它为克努特——莫里斯——普拉特操作(简称KMP算法)。KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配信息。我不喜欢叫他“看毛片”算法。但我不得不说,能联想到这个的人,确实很有才。

  原理如果文字理解起来非常复杂,而且有点难懂。因此,画图来讲解是最好的方式啦,下图非常容易理解算法的执行原理。

  我之前也是看了这幅图理解的。所以我觉得把这个图用来讲解最好不过(抱歉,我搬了这图,但是这个图是我至今觉得讲的最好的图,不得不搬),当然我已经全部重新画过。网络上KMP的讲解,讲的好的寥寥无几,发现一些博客,都是转载,或者讲的不够清楚,很难理解。我觉得有必要重新整理整理,自己来梳理一下知识点,为了让自己更理解深刻一些。

《4种字符串匹配算法:KMP(下),4种字符串匹配算法:BS朴素 Rabin-karp(上),4种字符串匹配算法:有限自动机(中)》

  首先模式串逐一对比文本串,如上图,直到遇到相同的元素,如下图:

《4种字符串匹配算法:KMP(下),4种字符串匹配算法:BS朴素 Rabin-karp(上),4种字符串匹配算法:有限自动机(中)》

   模式串,逐一对比,直到发现蓝色框框内的字符不相同,下图。这时候怎么办?

   BS算法,就是把模式串向前移动一位,从头继续比较,所以他的时间复杂度最差才是o(m*n)。而KMP呢,不再从头比较啦,这样大大减少了时间复杂度。我们即将引出next数组概念。

《4种字符串匹配算法:KMP(下),4种字符串匹配算法:BS朴素 Rabin-karp(上),4种字符串匹配算法:有限自动机(中)》

  既然,不保存,那他是怎么跳的呢?

  我们发现,ABCDAB,AB**AB, 这个字符串首尾相同,因此直接跳4格,如下图。

《4种字符串匹配算法:KMP(下),4种字符串匹配算法:BS朴素 Rabin-karp(上),4种字符串匹配算法:有限自动机(中)》

   也就是说,next数组保存的数和跳几格是有关系的呗。那我们怎么来看呢?这个字符串的匹配值有关。我们只要数,字符串首尾有几个是匹配的即可,通过这样来初始化。我们来看一下这个表格。

《4种字符串匹配算法:KMP(下),4种字符串匹配算法:BS朴素 Rabin-karp(上),4种字符串匹配算法:有限自动机(中)》

   A = 0  AB = 0  ABC = 0  ABCD = 0  ABCDA = 1  ABCDAB = 2  ABCDABD = 0

  公式:

移动位数 = 已匹配的字符数 - 表格内的匹配值

《4种字符串匹配算法:KMP(下),4种字符串匹配算法:BS朴素 Rabin-karp(上),4种字符串匹配算法:有限自动机(中)》

   我们继续看,即使跳转了4格,还是有蓝色的部分不匹配,又因为AB = 0 所以移动位数 = 已匹配的字符数(2) – 表格内的匹配值(0) = 2,依次类推,直到匹配到下图,则成功。

《4种字符串匹配算法:KMP(下),4种字符串匹配算法:BS朴素 Rabin-karp(上),4种字符串匹配算法:有限自动机(中)》

  该算法,最重要的是next数组上。理解这个,我们觉得其他就迎刃而解了。

 2、代码实现

  主要代码(c++版):

 1 std::map<int,int> compute_prefix(const std::string &pattern)
 2 {
 3     int i = 1;
 4     int p = 0;
 5     std::map<int, int> pi;
 6     int length = pattern.length();
 7     pi.insert(std::make_pair(1, 0));
 8     while (i < length)
 9     {
10         if (p > 0 && pattern[i] != pattern[p])
11         {
12             p = 0;
13         }
14         if (pattern[i] == pattern[p])
15         {
16             ++p;
17         }
18         pi.insert(std::make_pair(i + 1, p));
19         i++;
20     }
21     return pi;
22 }
23 
24 bool kmp_match(const std::string &text,const std::string &pattern)
25 {
26     std::map<int, int> pos;
27     pos = compute_prefix(pattern);
28     int q = 0;
29     for (int i = 0; i < text.length(); i++)
30     {
31         if (q > 0 && text[i] != pattern[q])
32         {
33             q = pos.at(q);
34         }
35         if (text[i] == pattern[q])
36         {
37             q++;
38         }
39         if (q == pattern.length())
40         {
41             return true;
42         }
43     }
44 }

测试代码:

int main()
{
    char a[] = "bbc abcdab abcdabcdabde";
    char b[] = "abcdabd";

    bool iftrue = kmp_match(a, b);;
    if (iftrue == true)
    {
        std::cout << "找到了" << std::endl;
    }
    else
    {
        std::cout << "没有" << std::endl;
    }
}

注:你也可以返回文本串的地址下标,稍加改动即可。

《4种字符串匹配算法:KMP(下),4种字符串匹配算法:BS朴素 Rabin-karp(上),4种字符串匹配算法:有限自动机(中)》
《4种字符串匹配算法:KMP(下),4种字符串匹配算法:BS朴素 Rabin-karp(上),4种字符串匹配算法:有限自动机(中)》

 1 int kmp_match(const std::string &text,const std::string &pattern)
 2 {
 3     std::map<int, int> pos;
 4     pos = compute_prefix(pattern);
 5     int q = 0;
 6     for (int i = 0; i < text.length(); i++)
 7     {
 8         if (q > 0 && text[i] != pattern[q])
 9         {
10             q = pos.at(q);
11         }
12         if (text[i] == pattern[q])
13         {
14             q++;
15         }
16         if (q == pattern.length())
17         {
18             return (i+1)-q+1;
19         }
20     }
21     return -1;
22 }

返回数据下标 稍加改动后的代码 点击打开

另外,从代码中可以看出,他的时间复杂度为o(n),预处理时间o(m)

资料:

特别感谢:阮一峰的网络日志 

《4种字符串匹配算法:KMP(下),4种字符串匹配算法:BS朴素 Rabin-karp(上),4种字符串匹配算法:有限自动机(中)》
《4种字符串匹配算法:KMP(下),4种字符串匹配算法:BS朴素 Rabin-karp(上),4种字符串匹配算法:有限自动机(中)》

 1 #include <iostream>
 2 #include <map>
 3 #include <string>
 4 #include <utility>
 5 #include <stdlib.h>
 6 
 7 std::map<int,int> compute_prefix(const std::string &pattern)
 8 {
 9     int i = 1;
10     int p = 0;
11     std::map<int, int> pi;
12     int length = pattern.length();
13     pi.insert(std::make_pair(1, 0));
14     while (i < length)
15     {
16         if (p > 0 && pattern[i] != pattern[p])
17         {
18             p = 0;
19         }
20         if (pattern[i] == pattern[p])
21         {
22             ++p;
23         }
24         pi.insert(std::make_pair(i + 1, p));
25         i++;
26     }
27     return pi;
28 }
29 
30 int kmp_match(const std::string &text,const std::string &pattern)
31 {
32     std::map<int, int> pos;
33     pos = compute_prefix(pattern);
34     int q = 0;
35     for (int i = 0; i < text.length(); i++)
36     {
37         if (q > 0 && text[i] != pattern[q])
38         {
39             q = pos.at(q);
40         }
41         if (text[i] == pattern[q])
42         {
43             q++;
44         }
45         if (q == pattern.length())
46         {
47             return (i+1)-q+1;
48         }
49     }
50     return -1;
51 }
52 
53 int main()
54 {
55     char a[] = "bbc abcdab abcdabcdabde";
56     char b[] = "abcdabd";
57 
58     int iftrue = kmp_match(a, b);;
59     if (iftrue >=0)
60     {
61         std::cout << "找到了" << " "<< iftrue <<std::endl;
62     }
63     else
64     {
65         std::cout << "没有" << std::endl;
66     }
67     system("pause");
68 }

完整代码

点赞