KMP匹配算法图解

问题:

对于一个源字符串 source = "abababaababacb" 来说,查找其中包含子串 pattern = "ababacb" 出现的位置下标。

 

首先,我们通过最基本的方法来进行查找。

i 表示当前用来匹配的 source 中字符的下标,j 表示当前用来匹配的模板的下标。

i

0

1

2

3

4

5

6

7

8

9

10

11

12

13

source

a

b

a

b

a

b

a

a

b

a

b

a

c

b

j

0

1

2

3

4

5

 

 

 

 

 

 

 

 

pattern

a

b

a

b

a

c

 

 

 

 

 

 

 

 

 

当 j = 5 的时候,我们可以发现,source[ 5 ] != pattern[ 5 ] , 问题是下一次使用 pattern 的哪个位置来重新开始匹配?

最简单的处理就是让 j = 0 , i 增加一个位置 i = 1,  然后,重新开始,下一次,i = 2,再重新开始,直到完成。

 

i

0

1

2

3

4

5

6

7

8

9

10

11

12

13

source

a

b

a

b

a

b

a

a

b

a

b

a

c

b

j

 

0

 

 

 

 

 

 

 

 

 

 

 

 

pattern

 

a

 

 

 

 

 

 

 

 

 

 

 

 

 

但是,我们对于我们的 pattern 来说,比较容易看到,pattern 下标为 2,3,4 的三个字符正好与 pattern 开始的三个字符相同,都是 "aba"

 

0

1

2

3

4

5

6

7

8

pattern

a

b

a

b

a

c

b

 

 

 

 

 

a

b

a

b

a

c

b

 

 

 

0

1

2

3

4

5

6

 

因此,并不需要让 j=0, 可以直接让 j=3, 从 3 开始比较,而 i 不变,这样可以减少 3 次以上的比较。

i

0

1

2

3

4

5

6

7

8

9

10

11

12

13

source

a

b

a

b

a

b

a

a

b

a

b

a

c

b

j

 

 

0

1

2

3

 

 

 

 

 

 

 

 

pattern

 

 

a

b

a

b

 

 

 

 

 

 

 

 

 

继续进行比较,直到 i=7,  j= 5 的时候,又不匹配了。

i

0

1

2

3

4

5

6

7

8

9

10

11

12

13

source

a

b

a

b

a

b

a

a

b

a

b

a

c

b

j

 

 

0

1

2

3

4

5

 

 

 

 

 

 

pattern

 

 

a

b

a

b

a

c

 

 

 

 

 

 

 

根据上一次的经验,我们又可以让 j =3, i 不变,然后进行匹配。但是还是不能匹配。

i

0

1

2

3

4

5

6

7

8

9

10

11

12

13

source

a

b

a

b

a

b

a

a

b

a

b

a

c

B

j

 

 

 

 

0

1

2

3

 

 

 

 

 

 

pattern

 

 

 

 

a

b

a

b

 

 

 

 

 

 

 

由于,我们还可以再调整一次 j = 1, 然而,还是不能匹配。

i

0

1

2

3

4

5

6

7

8

9

10

11

12

13

source

a

b

a

b

a

b

a

a

b

a

b

a

c

B

j

 

 

 

 

 

 

0

1

 

 

 

 

 

 

pattern

 

 

 

 

 

 

a

b

 

 

 

 

 

 

 

没有办法,只能让 j = 0 再次从头开始了。这次完全匹配。

I

0

1

2

3

4

5

6

7

8

9

10

11

12

13

Source

a

b

a

b

a

b

a

a

b

a

b

a

c

b

J

 

 

 

 

 

 

 

0

1

2

3

4

5

6

 

 

 

 

 

 

 

 

a

b

a

b

a

c

b

 

通过上面的分析,我们可以看到,由于模式串 pattern 内在的规律性,使得我们不必在每次匹配失败的时候,回溯到起始位置重新开始,而是可以利用已经匹配的部分来减少匹配的次数。如果我们提前通过预处理,构建出一个回溯的函数,就可以通过这个函数获取回溯的位置。

 

对于 pattern = "ababacb" 来说,我们可以构建一个回溯函数表 next ,列出在匹配失败的时候,应该回溯的位置。

对于 next[0] 来说,显然为 0.

对于 next[1] 来说,也只能从 0 再开始匹配。

以后的匹配值,需要参考上一次的匹配,如果继续匹配了,那么这个值应该增加 1,否则,应为最大匹配值。

对于  pattern = "ababacb"  的匹配函数如下:

pattern

a

b

a

b

a

c

b

j

0

1

2

3

4

5

6

next

0

0

 

但是,在这个匹配函数,对于 j 为 0,1,2 的情况来说,0, 1 是没有匹配,2 实际上匹配了第一次,这样的话,对于不匹配或者第一次匹配,next 函数都是 0, 不方便计算。

我们可以将 next 函数定义为当前是第几个匹配,这样的话,如果匹配了,就是大于 0 , 如果没有匹配就是 0 ,比较方便计算。

在进行匹配的过程中,匹配失败则取得上一个 next 函数的值,就可以取得当前位置之前的匹配。

pattern

a

b

a

b

a

c

b

j

0

1

2

3

4

5

6

next

0

0

 0

0

 

对于字节来说,匹配过程完全相同,构造一个 KMP 匹配函数的方法如下

复制代码
 1  public  int [] BuildKMP( byte [] pattern)
 2  {
 3       int [] next  =   new   int [pattern.Length];
 4 
 5      next[ 0 =   0 ;         //  第一个位置一定为 0
 6 
 7       int  j  =   0 ;           //  匹配的起始位置
 8       for  ( int  i  =   1 ; i  <  pattern.Length; i ++ )
 9      {
10           //  如果已经匹配上,但是现在不能匹配,回溯寻找
11           while ( j > 0   &&  pattern[j]  !=  pattern[i] )
12          {
13              j  =  next[j - 1 ];
14          }
15 
16           //  如果能够匹配上,向下推进一个位置
17           //  注意 i 在 for 循环中自动推进
18           if  (pattern[j]  ==  pattern[i])
19              j ++ ;
20 
21           //  保存
22          next[i]  =  j;
23      }
24       return  next;
25  }
复制代码

 

用于进行匹配的函数

复制代码
 1  public   static   int  KMPSearch( byte [] source,  byte [] pattern,  int  pos)
 2  {
 3       int  length  =  pattern.Length;
 4       int [] next  =  BuildKMP(pattern);
 5       int  j  =   0 ;
 6       for  ( int  i  =  pos; i  <  source.Length; i ++ )
 7      {
 8           while  (j  >   0   &&  source[i]  !=  pattern[j])
 9              j  =  next[j - 1 ];       //  调整下一个匹配的位置
10           if  (source[i]  ==  pattern[j])
11              j ++ ;
12           if  (j  ==  length)
13               return  i - length  + 1 ;
14      }
15       return   - 1 ;
16  }
复制代码
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值