KMP算法next数组的理解


KMP算法的基础部分不再多说,详细大家都Google过了。这里做一些总结。

对于KMP算法来说,重点就是 next数组 (也有叫覆盖函数,部分匹配表,lps数组等)。

总之就是 对模式串做预处理,而且该预处理只和 模式串(pattern)本身有关!

假设有模式串 pattern = “abababca”;  则有匹配表:

1 char:  | a | b | a | b | a | b | c | a |
2 index: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
3 value: | 0 | 0 | 1 | 2 | 3 | 4 | 0 | 1 |

 

下面介绍《部分匹配表》是如何产生的。

首先,要了解两个概念:”前缀”和”后缀”。 “前缀”指除了最后一个字符以外,一个字符串的全部头部组合;”后缀”指除了第一个字符以外,一个字符串的全部尾部组合。

1 字符串:   "bread"
2  
3 前缀: b , br, bre, brea
4  
5 后缀:  read, ead, ad ,  d

 

关于 next数组 (也有叫覆盖函数,部分匹配表,lps数组) 的

通俗解释:”部分匹配值”就是”前缀”和”后缀”的最长的共有元素的长度。以”ABCDABD”为例,

1 - "A"的前缀和后缀都为空集,共有元素的长度为0;
2 - "AB"的前缀为[A],后缀为[B],共有元素的长度为0;
3 - "ABC"的前缀为[A, AB],后缀为[BC, C],共有元素的长度0;
4 - "ABCD"的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为0;
5 - "ABCDA"的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],共有元素为"A",长度为1;
6 - "ABCDAB"的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为"AB",长度为2;
7 - "ABCDABD"的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度为0。

KMP算法next数组的理解_第1张图片

1 pattern “AABAACAABAA”, next[] is [0, 1, 0, 1, 2, 0, 1, 2, 3, 4, 5]
2 pattern “ABCDE”, next[] is [0, 0, 0, 0, 0]
3 pattern “AAAAA”, next[] is [0, 1, 2, 3, 4]
4 pattern “AAABAAA”, next[] is [0, 1, 2, 0, 1, 2, 3]
5 pattern “AAACAAAAAC”, next[] is [0, 1, 2, 0, 1, 2, 3, 3, 3, 4]

下面的代码实现中,lps(longest prefix suffix )数组就是我们说的next数组。这个是最原创的实现,和网上很多优化的next数组的算法不同,但是基本原理是一样的:

01 #include<stdio.h>
02 #include<string.h>
03 #include<stdlib.h>
04  
05 void computeLPSArray(char *pat, int M, int *lps);
06  
07 void KMPSearch(char *pat, char *txt)
08 {
09     int M = strlen(pat);
10     int N = strlen(txt);
11  
12     // 预处理pattern,计算出 lps[]数组记录前缀和后缀的最长匹配
13     int *lps = (int *)malloc(sizeof(int)*M);
14     int j  = 0;  // index for pat[]
15  
16     // Preprocess the pattern (calculate lps[] array)
17     computeLPSArray(pat, M, lps);
18  
19     int i = 0;  // index for txt[]
20     while(i < N)
21     {
22       if(pat[j] == txt[i])
23       {
24         j++;
25         i++;
26       }
27  
28       if (j == M)
29       {
30         printf("Found pattern at index %d \n", i-j);
31         j = lps[j-1];
32       }
33  
34       // mismatch after j matches
35       else if(pat[j] != txt[i])
36       {
37         // Do not match lps[0..lps[j-1]] characters,
38         // they will match anyway
39         if(j != 0)
40          j = lps[j-1];
41         else
42          i = i+1;
43       }
44     }
45     free(lps); // to avoid memory leak
46 }
47  
48 void computeLPSArray(char *pat, int M, int *lps)
49 {
50     int len = 0;  // 记录前一个[最长匹配的前缀和后缀]的长度
51     int i;
52  
53     lps[0] = 0; // lps[0] 必须是 0
54     i = 1;
55  
56     // the loop calculates lps[i] for i = 1 to M-1
57     while(i < M)
58     {
59        if(pat[i] == pat[len])
60        {
61          len++;
62          lps[i] = len;
63          i++;
64        }
65        else // (pat[i] != pat[len])
66        {
67          if( len != 0 )
68          {
69            // 这个地方有陷阱. 考虑这个例子 AAACAAAA ,i = 7.
70            len = lps[len-1];
71  
72            // 另外, 注意 i 在这个地方并没有增加
73          }
74          else // 如果 (len == 0)
75          {
76            lps[i] = 0; //没有一个匹配的
77            i++;
78          }
79        }
80     }
81 }
82  
83 // 测试
84 int main()
85 {
86    char *txt = "ABABDABACDABABCABAB";
87    char *pat = "ABABCABAB";
88    KMPSearch(pat, txt);
89    return 0;
90 }

测试数据如下:

1) Input:

1 txt[] =  "THIS IS A TEST TEXT"
2 pat[] = "TEST"

Output:

1 Pattern found at index 10

2) Input:

1 txt[] =  "AABAACAADAABAAABAA"
2 pat[] = "AABA"

Output:

1 Pattern found at index 0
2 Pattern found at index 9
3 Pattern found at index 13


next[0]=0 的初始化在下面的计算中并不方便,大多数算法都是初始化 next[0] = -1; 而且只有next[0]为-1,其他next[i] >= 0.

下面给出更常见的写法:

01 //常规写法,求next数组的值
02 void getNext()
03 {
04     int i = 0; //pattern串的下标
05     int j = -1; //
06     next[0] = -1;
07     while (i < pattern_len - 1)
08     {
09         if (j == -1 || pattern[i] == pattern[j])
10         {
11             ++i;
12             ++j;
13             next[i] = next[j];
14         }
15         else
16             j = next[j];
17     }
18 }

一样的原理,只是next的数组的含义可能稍有偏差。

但总体来说,Next数组意义:当pattern串失配的时候,NEXT数组对应的元素指导应该用patter串的哪个元素进行下一轮的匹配。

上面的算法是有缺陷的。比如我们的模式串  pattern =“AAAAB”,其中很容易得到next数组为01230。

如果目标匹配串为 “AAAACAAAAB” ,大家可以模拟一下,A要回溯多次。

就是说我们的next数组优化并不彻底。

优化算法:next[i]表示匹配串在i处如果匹配失败下次移到的位置.

 

最终的优化算法代码:

01 //优化算法,求next数组的值
02 void getNext2()
03 {
04     int i = 0; //pattern串的下标
05     int j = -1; //
06     next[0] = -1;
07     while (i < pattern_len - 1)
08     {
09  
10         if (j == -1 || pattern[i] == pattern[j])
11         {
12             ++i;
13             ++j;
14             if (pattern[i] != pattern[j]) //正常情况
15                 next[i] = j;
16             else //特殊情况,这里即为优化之处。考虑下AAAAB, 防止4个A形成0123在匹配时多次迭代。
17                 next[i] = next[j];
18         }
19         else
20             j = next[j];
21     }
22 }

虽然两种写求得next值不一样,但是kmp函数的写法是一样的。

参考测试代码:

001 #include <iostream>
002 #include <stdio.h>
003 #include <string.h>
004 using namespace std;
005  
006 int next[100], pattern_len, str_len;
007 char * pattern, * str;
008  
009 //优化算法,求next数组的值
010 void getNext2()
011 {
012     int i = 0; //pattern串的下标
013     int j = -1; //
014     next[0] = -1;
015     while (i < pattern_len - 1)
016     {
017  
018         if (j == -1 || pattern[i] == pattern[j])
019         {
020             ++i;
021             ++j;
022             if (pattern[i] != pattern[j]) //正常情况
023                 next[i] = j;
024             else //特殊情况,这里即为优化之处。考虑下AAAAB, 防止4个A形成0123在匹配时多次迭代。
025                 next[i] = next[j];
026         }
027         else
028             j = next[j];
029     }
030 }
031  
032 //求next数组的值
033 void getNext()
034 {
035     int i = 0; //pattern串的下标
036     int j = -1; //
037     next[0] = -1;
038     while (i < pattern_len - 1)
039     {
040         if (j == -1 || pattern[i] == pattern[j])
041         {
042             ++i;
043             ++j;
044             next[i] = j;
045         }
046         else
047             j = next[j];

你可能感兴趣的:(KMP算法next数组的理解)