字符串匹配之暴力匹配及kmp算法Java实现

一、基本概念

字符串匹配是计算机科学中最古老、研究最广泛的问题之一。一个字符串是一个定义在有限字母表∑上的字符序列。例如,ATCTAGAGA是字母表∑ = {A,C,G,T}上的一个字符串。字符串匹配问题就是在一个大的字符串T中搜索某个字符串P的所有出现位置。其中,T称为文本,P称为模式,T和P都定义在同一个字母表∑上。

  对于字符串匹配问题,我们最直接能想到的就是暴力匹配,由于暴力匹配算法时需要大量的回溯,所以虽然可以解决问题,但其效率是非常低的。而为了解决这一问题,我们可以使用KMP算法来解决字符串匹配问题。
  KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。KMP算法的时间复杂度O(m+n)。

二、思路分析

现假设有字符串str1="BBC ABCDAB ABCDABCDABDE"和一个字串str2=“ABCDABD”,需要判断str1中是否存在str2,若存在则返回第一次出现的位置,否则返回-1

1.暴力匹配算法(不推荐):

如果使用暴力匹配算法,并假设现在str1匹配到i位置,子串str2匹配到j位置,则有:

  1. 如果当前字符串匹配成功,即str1[i] = str2[j],则i++,j++,继续匹配下一个字符。
  2. 如果匹配失败,即str1[i] != str2[j],令i = i - (j - 1),j = 0,相当于每次匹配失败,i回溯,j置为0。
  3. 直到str2完全进行匹配并返回 i = i - j。

2.KMP算法(推荐)

KMP算法的详细实现原理可以参考:
链接: KMP算法原理详解

  1. 算法步骤:
    1. 根据待匹配字符串得到部分匹配表
    2. 对字符串进行匹配
      1. 如果两个字符串的第一个字符不符合就i++,j++
      2. 重复1,直到str1的某个字符与str2的某个字符符合,即str1[i] = str2[0]
      3. 当后面再次出现str1[i] != str2[j]的情况时,不再像暴力匹配算法一样进行回溯到已经比较过的位置,而是根据部分匹配表来进行继续向后移动,移动位数 = 已匹配的字符数 - 对应的部分匹配值。
    3. 直到直到str2完全进行匹配并返回 i = i - j + 1。
      KMP算法的几本匹配步骤和暴力算法是类似的,只是在匹配失败时,KMP算法不再是回溯到已经匹配过的位置,而是根据部分匹配值继续后移来提高效率。
  2. 部分匹配表
    先简单介绍一下字符串的前缀和后缀:
    在这里插入图片描述

关于这个部分匹配表的计算,实际上就是计算字符串的前缀和后缀的最长公共元素的长度。这里我们以ABCDABD为例,来进行分析。

  • "A"的前缀和后缀都为空,所以共有元素长度为0
  • "AB"的前缀为[A],后缀为[B],所以共有元素长度为0
  • "ABC"的前缀为[A,AB],后缀为[C,BC],所以共有元素长度为0
  • "ABCD"的前缀为[A,AB,ABC],后缀为[D,CD,BCD],所以共有元素长度为0
  • “ABCDA"的前缀为[A,AB,ABC,ABCD],后缀为[A,DA,CDA,BCDA],共有元素为“A”,所以共有元素长度为1
  • “ABCDAB"的前缀为[A,AB,ABC,ABCD,ABCDA],后缀为[B,AB,DAB,CDAB,BCDAB],共有元素为“AB”,所以共有元素长度为2
  • "ABCDABD"的前缀为[A,AB,ABC,ABCD,ABCDA,ABCDAB],后缀为[D,BD,ABD,DABD,CDABD,BCDABD],所以共有元素长度为0

所以我们可以得到以下一个部分匹配表

搜索词ABCDABD
部分匹配值0000120

部分匹配的实质是,有时候,字符串头部和尾部会有重复。比如,”ABCDAB“之中有两个”AB“,那么它的部分匹配值就是2("AB"的长度),搜索词移动的时候,第一个”AB“向后移动4位(字符串长度-部分匹配值)就可以来到第二个"AB"的位置。

三、代码实现

1.暴力匹配算法代码实现

/**
 * @author dankejun
 * @create 2020/9/1811:25
 */
public class ViolenceMatch {
    public static void main(String[] args) {
        String str1 = "BBC ABCDAB ABCDABCDABDE";
        String str2 = "ABCDABD";
        int index = violenceMatch(str1, str2);
        System.out.println("str2第一次出现的位置为:" + index);
    }

    //暴力匹配算法
    public static int violenceMatch(String str1, String str2) {
        char[] s1 = str1.toCharArray();
        char[] s2 = str2.toCharArray();

        int s1Len = s1.length;
        int s2Len = s2.length;

        int i = 0;
        int j = 0;
        while (i < s1Len && j<s2Len) {
            if (s1[i] == s2[j]) {
                i++;
                j++;
            } else {
                i = i - (j - 1);
                j = 0;
            }
        }
        if (j == s2Len) {
            return i - j;
        } else return -1;
    }
}

测试结果:
在这里插入图片描述

2.KMP算法代码实现

/**
 * @author dankejun
 * @create 2020/9/1815:58
 */
public class KMPAlgorithm {
    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("str2第一次出现的位置为:" + index);
    }

    public static int kmpSearch(String str1, String str2, int[] next) {
        for (int i = 0, j = 0; i < str1.length(); i++) {
            //匹配未完成且匹配失败,j根据最后一个匹配字符的部分匹配值继续后移
            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) {
        //保持部分匹配值
        int[] next = new int[dest.length()];
        next[0] = 0;//如果字符串长度为1,部分匹配值为0
        for (int i = 1, j = 0; i < dest.length(); i++) {
            while (j > 0 && dest.charAt(i) != dest.charAt(j)) {
                j = next[j - 1];
            }

            if (dest.charAt(i) == dest.charAt(j)) {
                j++;
            }
            next[i] = j;
        }
        return next;
    }
}

测试结果:
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值