编程中十大常用算法:(四)KMP算法

背景

字符串匹配问题:
(1)有一个字符串str1=“BBC ABCDAB ABCDABCDABDE”,和一个子串str2=“ABCDABD”
(2)现在要判断str1中是否含有str2,如果存在,就返回第一次出现的位置,如果没有,则返回-1

暴力匹配算法:
利用暴力匹配算法,假设现在str1匹配到i位置,子串str2匹配到j位置,则有:
1)如果当前字符串匹配成功(即str1[i]=str[j]),则i++,j++,继续匹配下一个字符
2)如果匹配失败(即str1[i]!=str[j]),则i=i-(j-1),j=0。相当于每次匹配失败时,i回溯,j被设为0。

存在问题:
利用暴力算法解决此类问题会有大量的回溯,每次只移动一位,若不匹配,移动到下一位接着判断,计算时间复杂度较大(O(nm): n为字符串str1的长度,m为字符串str2的长度)。

解决办法:
KMP算法

KMP算法理论介绍

克努特—莫里斯—普拉特操作(Knuth-Morris-Pratt, KMP),是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。
KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。通过一个next数组,保存模式串中前后最长公共子序列的长度,每次回溯时,通过next数组找到前面匹配过的位置,省去了大量的计算时间。

举例说明

字符串匹配问题:
1)有一个字符串str1=“BBC ABCDAB ABCDABCDABDE”,和一个子串str2=“ABCDABD”
2)判断str1是否含有str2,如果存在,则返回第一次出现的位置,如果没有,则返回-1

KMP算法思路分析图解:
(1)首先,用str1的第一个字符和str2的第一个字符去比较,不符合,关键词向后移动一位
在这里插入图片描述
(2)重复第一步,若还是不符合,再次后移
在这里插入图片描述
(3)一直重复,直到str1有一个字符与str2的第一个字符匹配为止在这里插入图片描述
(4)接着比较str1与str2的下一个字符,还是匹配
在这里插入图片描述
(5)遇到str1有一个字符与str2的字符不匹配
在这里插入图片描述
(6)此时,如果利用暴力匹配算法时,将会重复第一步(这是很不明智地,因为此时BCD已经比较过了,没有必要在做重复地工作,一个基本事实为,当空格与D不匹配时,你已经知道前六字符为"ABCDAB"。KMP的思想是:利用这个已知信息,不把搜索位置移回已经比较过的位置,继续把它向后移,这样就可以提供效率)
在这里插入图片描述
(7)如何把刚刚重复的步骤省略掉?可以对str2计算出一张”部分匹配表“,此表的产生后面介绍
在这里插入图片描述
(8)已知空格与D不匹配时,前面6个字符"ABCDAB"是匹配的。查表可知,最后一个匹配字符B对应的”部分匹配值“为2,因此按照下面的公式算出向后移动的位数:
移动位数=已匹配的字符数-对应的部分匹配值(6-2=4,因此将str2向后移动4位)

(9)因为空格与C不匹配,str2还有向后移动。此时,已匹配的字符数位2(“AB”),对应的”部分匹配值“为0,移动位数=2-0,于是将str2后移2位
在这里插入图片描述

(10)因为空格与A不匹配,继续后移一位
在这里插入图片描述
(11)逐位比较,知道发现C与D不匹配。因此,移动位数=6-2,将str2后移4位
在这里插入图片描述
(12)逐位比较,直到str2的最后一位,发现完全匹配,搜索结束
在这里插入图片描述
(13)”部分匹配表的产生“,首先介绍前缀和后缀
在这里插入图片描述
”部分匹配值“就是”前缀“和”后缀“的最长的共有元素的长度。以"ABCDABD"为例:
"A"的前缀和后缀均为空集,共用长度为0;
"AB"的前缀为[A],后缀为[B],共用元素的长度为0;
"ABC"的前缀为[A,AB],后缀为[BC,C]共用元素的长度为0;
"ABCD"的前缀为[A,AB,ABC],后缀为[BCD,CD,D],共用元素长度为0;
“ABCDA"的前缀为[A,AB,ABC,ABCD],后缀为[BCDA,CDA,DA,A],共用元素为"A”,长度为1;
“ABCDAB"的前缀为[A,AB,ABC,ABCD,ABCDA],后缀为[BCDAB,CDAB,DAB,AB,B],共用元素为"AB”,长度为2;
"ABCDABD"的前缀为[A,AB,ABC,ABCD,ABCDA,ABCDAB],后缀为[BCDABD,CDABD,DABD,ABD,BD,D]共用元素长度为0。

(14)”部分匹配“的实质为,有时字符串头部和尾部会有重复。如”ABCDAB“之中有两个"AB",那么它的”部分匹配值“为2(”AB“的长度)。str2移动时,第一个"AB"向后移动4位(字符串长度-部分匹配值),就可以来到第二个"AB"的位置。在这里插入图片描述

代码实现(Java)



package com.zq.kmp;

import java.util.Arrays;

public class KMP {

  public static void main(String[] args) {

     String str1="BBC ABCDAB ABCDABCDABDE";

     String str2="ABCDABD";

     int[] next=kmpnext(str2);

   System.out.println("next="+Arrays.toString(next));

     int index=kmpSearch(str1, str2, next);

     System.out.println("index="+index);

  }

  
  /*

   * KMP匹配算法

   * */

  public static int kmpSearch(String str1,String str2,int[] next) {

     for(int i=0,j=0;i<str1.length();i++) {

       while(j>0 && str1.charAt(i)!=str2.charAt(j)) {

          j=next[j-1];

       }

       if (str1.charAt(i)==str2.charAt(j)) {

          j++;

       }

       if (j==str2.length()) {

          return i-j+1;

       }

     }

     return -1;

  }

 
  /*

   * 获取一个字符串的部分匹配表

   * */

  public static int[] kmpnext(String dest) {

     //创建数组next保存部分匹配值

     int[] next=new int[dest.length()];

     next[0]=0;

     for(int i=1,j=0;i<dest.length();i++) {

       //当dest.charAt(i)!=dest.charAt(j),需要从next[j-1]获取新的j

       //直到发现有dest.charAt(i)=dest.charAt(j)成立时退出

       while(j>0 && dest.charAt(i)!=dest.charAt(j)) {

          j=next[j-1];

       }

       //当dest.charAt(i)=dest.charAt(j)时,部分匹配值加一

       if (dest.charAt(i)==dest.charAt(j)) {

          j++;

       }

       next[i]=j;

     }

     return next;

  }

}

 

仿真结果

在这里插入图片描述
参考文献:
1.韩顺平-图解Java数据结构和算法
2.https://www.cnblogs.com/zzuuoo666/p/9028287.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值