KMP算法

众所周知,KMP算法的作用是匹配字符串,比如在一个文本编辑器中查找符合某一模式的所有字符等。

就我认为,这个算法还是比较难的,反正我是看了好长时间都没懂,哈哈~

昨天说了,每天一个算法,坚持下去!

今天看了《算法导论》中关于该算法的部分,对求next数组部分不是特别理解,参考了网上的某些文章,不过他这篇文章的分析确实不错,但代码部分有些缺陷,我已改正过来。

简要记录如下:

<!--more-->

普通的暴力匹配算法是将模式串和文本串中的每一个可能的位置都进行逐个比较,假设文本串长度为n,模式串长度为m,那么它的时间复杂度就是O(mn)。其缺点是如果模式串中有重复的字符,并没有很好地利用之前比较过的信息,导致做了一些不必要的比较操作。

而KMP算法则是有一个称之为next的数组,该数组记录了模式串中从开始到当前位置前一个的子串中可以匹配的最大前缀和后缀的长度。有点绕口是吧?来看一个例子:

假设我们有字符串string str=“ABABABC",则next数组长度为str.size()==7。那么:

  • next[0]对应的”从开始到当前位置前一个的子串“为空,所以next[0]=-1;
  • next[1]对应的“从开始到当前位置前一个的子串”为“A”,其前缀和后缀均为空,所以next[1]=0;
  • next[2]对应的“从开始到当前位置前一个的子串”为“AB”,它的前缀为“A”,后缀为“B”(空前缀和空后缀没有写出,下同),没有相等的,所以next[2]=0;
  • next[3]对应的“从开始到当前位置前一个的子串”为“ABA”,前缀有“A”,“AB”,后缀有“BA”,“A”,它们中可以匹配的有“A”,长度为1,所有next[3]=1;
  • next[4]对应的“从开始到当前位置前一个的子串”为“ABAB”,前缀有“A“,”AB“,”ABA“,后缀有”BAB“,”AB“,”B”,匹配的有“AB”,长度为2,所有next[4]=2;
  • next[5]对应的“从开始到当前位置前一个的子串”为“ABABA”,前缀有“A”,“AB”,”ABA”,”ABAB”,后缀有”BABA”,”ABA”,”BA”,”A”,匹配的有“A”和“ABA”,最长的为“ABA”,长度为3,所以next[5]=3;
  • next[6]对应的“从开始到当前位置前一个的子串”为“ABABAB”,前缀有“A”,”AB”,”ABA”,”ABAB”,”ABABA”,后缀有”BABAB”,”ABAB”,”BAB”,”AB”,”B”,匹配的有“AB”,”ABAB”,最长的一个为”ABAB“,长度为4,所以next[6]=4;

而如何用代码求出next数组,我认为这是KMP算法的一个难点。具体可以点击上文这篇博客链接。

具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//暴力匹配
void NaiveStringMatcher(const string&amp; text, const string&amp; pattern)
{
int n = text.size();
int m = pattern.size();
int i;
int s;
for (s = 0;s &lt;= n - m;++s)
{
for (i = 0;i &lt; m;++i)
{
if (text[s + i] != pattern[i])
break;
}
if (i == m)
cout &lt;&lt; "Pattern occurs with shift " &lt;&lt; s &lt;&lt; endl;
}
}
void CalcNext(vector&lt;int&gt;&amp; next, const string&amp; p)
{
int length = p.size();
next.resize(length);
next[0] = -1;
int k = -1, j = 0;
while(j &lt; length-1)
{
while (k &gt;= 0 &amp;&amp; p[j] != p[k])
k = next[k];
k++;
j++;
next[j] = k;
}
}
void KMP(const string&amp; text, const string&amp; pattern)
{
int tlen = text.size();
int plen = pattern.size();
vector&lt;int&gt; next;
CalcNext(next, pattern);
int i, j=0;
for (i = 0;i &lt; tlen;++i)
{
while (j &gt; 0 &amp;&amp; text[i] != pattern[j])
j = next[j];
if (text[i] == pattern[j])
j++;
if (j == plen)
{
cout &lt;&lt; "Pattern occurs with shift " &lt;&lt; i - plen + 1 &lt;&lt; endl;
j = next[j-1];
}
}
}

github地址在这里