KMP算法

简介

KMP算法,全称为Knuth-Morris-Pratt子字符串查找算法。Knuth、Morris和Pratt发明的这个算法的基本思想是在当字符串出现不匹配时,就能知晓一部分文本的内容(因为在匹配失败之前它们已经和模式相匹配)。我们可以利用这些信息避免将指针回退到所有这些已知的字符之前。

举例:
在正文中查找模式字符串BAAAAAAAAA
例如在正文ABAAAABAAAAAAAAA中查找模式字符串BAAAAAAAAA,在匹配过程中,我们会发现当我们匹配到第六个字符时(第一个字符为B)匹配失败,文本指针现在指向的是末尾的字符B。我们可以观察到,这里不需要回退文本指针i,因为正文中的第六个字符B前有四个A,均与模式的第一个字符不匹配,另外,i当前指向的字符B和模式的第一个字符向匹配,所以可以直接将i + 1,以比较文本中的下一个字符和模式中的第二个字符,从而继续查找。
在匹配失败时,如果模式字符串中的某处可以和匹配失败处的正文相匹配,那么久不应该完全跳过所有已经匹配的字符。KMP算法的主要思想是提前判断如何重新开始查找,而这种判断只取决于模式本身。

1、模式指针的回退

在KMP子字符串查找算法中,不会回退文本指针i,而是使用一个数组dfa[ ][ ]来记录匹配失败时模式指针j应该回退多远。
在这里插入图片描述
对于每个字符c,在比较了c和pat.charAt(j)之后,dfa[c][j]表示的是应该和下个文本字符比较的模式字符的位置。
在查找中,dfa[txt.charAt(i)][j]是在比较了txt.charAt(i)和pat.charAt(j)之后应该和txt.charAt(i+1)比较的模式字符位置。在匹配时会继续比较下一个字符,因此dfa[pat.charAt(j)][j]总是j + 1。
在不匹配时,不仅可以知道txt.charAt(i)的字符,也可以知道正文中的前j - 1个字符,它们就是模式中的前j - 1个字符。
可与滑动窗口匹配字符串的方法进行比较。

2、KMP查找算法

只要计算出来dfa[ ][ ]数组,就得到了后面所示的子字符串查找算法(DFA模拟):
当 i 和 j 所指向的字符匹配失败时(从文本的i - j + 1处开始检查模式的匹配情况),模式可能匹配的下一位置应该从i - dfa[txt.charAt(i)][j]处开始。按照算法,从该位置开始的dfa[txt.charAt(i)][j]个字符和模式的前dfa[txt.charAt(i)][j]个字符应该相同,因此无需回退指针i,只需要将j设为dfa[txt.charAt(i)][j]并将 i 加上 1即可,这正是当 i 和 j 所指向的字符匹配时的行为。

3、DFA模拟

先上代码:

//KMP子字符串查找算法(DFA模拟)
public int search(String txt){
//模拟DFA处理文本txt时的操作
   int i, j, N = txt.length(), M = pat.length();
   for(i = 0, j = 0; i < N && j < M; i++){
      j = dfa[txt.charAt(i)][j];
   }
   if(j == M) return i - M;//找到匹配
   else       return N; //未找到匹配

说明这个过程的一种较好的方法是使用确定有限状态自动机(DFA)
这和我们在电子电路中学到的状态图很相似,看图还是很好理解的。
在这里插入图片描述
KMP的字符串查找方法search()只是一段模拟自动机运行的Java程序。
在这里插入图片描述

4、构造DFA

解决KMP算法的关键问题:如何计算给定模式相对应的dfa[ ][ ]数组?
Knuth、Morris、和Pratt发明了一种巧妙(但也很复杂)的构造方式。

dfa[pat.charAt(0)][0] = 1;
for (int x = 0, j = 1; j < M; j++){
//计算dfa[][j]
   for (int c = 0; c < R; c++){
      dfa[c][j] = dfa[c][X];
   }
   dfa[pat.charAt(j)][i] = j + 1;
   X = dfa[pat.charAt(j)][X];
}

对于每个 j ,它将会:
将dfa[ ][X]复制到dfa[ ][j](对于匹配失败的情况)。
将dfa[pat.charAt(j)][j] 设为 j + 1(对于匹配成功的情况)。
更新X。
在这里插入图片描述

KMP字符串查找算法

public class KMP
{
   private String pat;
   private int[][] dfa;
   public KMP(String pat)
   {     //由模式字符串构造DFA
      this.pat = pat;
      int M = pat.length();
      int R = 256;
      dfa = new int[R][M];
      dfa[pat.charAt(0)][0] = 1;
      for (int X = 0, j = 1; j < M; j++)
      {     //计算dfa[][j]
         for (int c = 0; c < R; c++)
         {
            dfa[c][j] = dfa[c][X];//复制匹配失败情况下的值
         }
         dfa[pat.charAt(j)][j] = j + 1;//复制匹配成功情况下的值
         X = dfa[pat.charAt(j)][X];//更新重启状态
      }
   }
   public int search(String txt)
   {     //在txt上模拟DFA的运行
      int i, j, M = txt.length(), M = pat.length();
      for (i = 0, j = 0; i < N && j < M; i++)
      {
         j = dfa[txt.charAt(i)][j];
      }
      if(j == M) return i - M;//找到匹配(到达模式字符串的结尾)
      else       return N;//未找到匹配(到达文本字符串的结尾)
   }
   public static void main(String[] args){}
}

对于长度为M的模式字符串和长度为N的文本,KMP字符串查找算法访问的字符不会超过M+N个。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值