【KMP算法和暴力求解法】字符串匹配

算法的精妙不在于代码,而在于思想

暴力求解法

从字符串 str1中匹配,子字符串 str2

  • 如果当前字符匹配成功, 即 str1[i]==str2[j], 则 i++; j++; 继续匹配下一个字符
  • 如果当前字符匹配失败, 则 i=i-(j-1); j=0; 也就是每次匹配失败时, i回溯, j被置为0

图解:
在这里插入图片描述

  • 1、我们比较i指针指向的字符和j指针指向的字符是否一致。如果一致就都向后移动,如果不一致,如下图:
    在这里插入图片描述
    *2、 A和E不相等,那就把i指针移回第1位(假设下标从0开始),j移动到模式串的第0位,然后又重新开始这个步骤:
    在这里插入图片描述
    代码:
/**
 * 暴力破解法
 * @param ts 主串
 * @param ps 模式串
 * @return 如果找到,返回在主串中第一个字符出现的下标,否则为-1
 */

public static int bf(String ts, String ps) {
    
    
    char[] t = ts.toCharArray();
    char[] p = ps.toCharArray();
    int i = 0; // 主串的位置
    int j = 0; // 模式串的位置
    while (i < t.length && j < p.length) {
    
    
       if (t[i] == p[j]) {
    
     // 当两个字符相同,就比较下一个
           i++;
           j++;
       } else {
    
    
           i = i - j + 1; // 一旦不匹配,i后退
           j = 0; // j归0
       }
    }
    if (j == p.length) {
    
    
       return i - j;
    } else {
    
    
 return -1;
    }
}

分析:上面这个步骤把 i 回到第一位是很不明智的,因为我们 BC 已经比较过了, 也就是:因为主串匹配失败的位置前面除了第一个A之外再也没有A了,我们为什么能知道主串前面只有一个A?因为我们已经知道前面三个字符都是匹配的!(这很重要)。移动过去肯定也是不匹配的!有一个想法,i可以不动,我们只需要移动j即可,如下在这里插入图片描述

KMP 算法

KMP算法的介绍

  • Knuth-Morris-Pratt 字符串查找算法,简称为 “KMP算法”,常用于在一个文本串S内查找一个模式串P 的出现位置,这个算法由Donald Knuth、Vaughan Pratt、James H. Morris三人于1977年联合发表,故取这3人的姓氏命名此算法
  • KMP算法就是利用之前判断过的信息,通过一个 next 数组,保存模式串中前后,最长的公共子序列的长度,每次回溯时,通过next数组找到,前面匹配过的位置,省去大量的时间

所以现在搞懂 KMP算法 就要明白两件事,什么是 next数组?怎么用它?

过程: 文本串S“BBC ABCDAB ABCDABCDABDE”,和模式串P“ABCDABD”,拿模式串P去跟文本串S匹配,整个过程如下所示:

  • 1、先用str1 中的第一个字符和str2 第一个字符去比较,不符合,关键词向后移动一位

在这里插入图片描述
2、一直重复第一步,直到str1 有一个字符与 str2 的第一个字符匹配为为止:
在这里插入图片描述
3、接着比较字符串和搜索词,直到str1 中有一个字符和 str2 对应的字符不符合:

在这里插入图片描述

  • 这时候,想到的是继续遍历Str1 的下一个字符,重复第 1 步。但是这是没有必要的,因为我们的 BCD 已经比较过了,没有必要继续做重复工作,当上面:空格 和 D 不匹配时候,我们其实知道了前面 6 个字符是:“ABCDAB”。
    KMP算法的想法是:设法利用这个已知信息,不要把“搜索位置”
    移回到已经比较过的位置,继续把它后移,这样就提高了效率!

解上述疑:上面解释中大家可能不理解 BCD 已经比较过是什么意思,我来画图分析:
在这里插入图片描述

  • ok,那我们现在的问题就是:怎么知道 1 号球 和 2,3号球颜色不同呢?也就是怎么知道上面的 A 和 后面的 BCD 不相同呢?
    请继续往下看

4、怎么做到把刚刚的重复步骤给省略?可以对 str2 计算出一张《部分匹配表》,这张表怎么产生的,后面介绍
在这里插入图片描述
5、已经空格和 D 不匹配,前6 个字符“ABCDAB”是匹配的,查表可知,最后一位匹配字符 B 对应的“部分匹配值”为 2,因此按照下面公式移动位数:
移动位数 = 已匹配字符数 - 对应的部分匹配表值
及就是:6 - 2 = 4 所以将搜索词向后移动四位
在这里插入图片描述
6、上面空格和 C 不匹配,搜索词继续后移。这时,已匹配的字符数为2 (“AB”),对应的”部分匹配值”为 0 ,所以:移动位数 = 2 - 0 = 2,两位
在这里插入图片描述
7、因为上面空格和 A不匹配,所以继续后移一位
在这里插入图片描述
8、上面逐位比较,发现 C 和 D 不匹配,于是移动位数: 6 - 2 = 4 位
在这里插入图片描述
成功!

9、《部分匹配表》
在这里插入图片描述
“部分匹配值”就是 “前缀” 和 “后缀” 的最长的共有元素的长度!

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

  • 综上可得实现KMP算法两大步骤:
  • 1、先得到字串的部分匹配表
  • 2、使用部分匹配表完成KMP算法

代码实现:

public class Main21 {
    
    
    public static void main(String[] args) {
    
    
        String str1 = "ABCDABC";
        String str2 = "ABCE";
        int[]next = kmpNext(str2);

        System.out.println(kmpSearch(str1,str2,next));
    }

    /**
     *
     * @param str1  源字符串
     * @param str2 字串
     * @param next 部分匹配表
     * @return  -1:没有匹配到   匹配到:返回初始位置
     */
    public static int kmpSearch(String str1,String str2,int[]next){
    
    
        for (int i = 0,j = 0;i<str1.length();i++){
    
    
            //需要处理当str1.charAt(i) != str2.charAt(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) {
    
    
        //KMP  获取字串 部分匹配表
        //创建一个next 数组保存部分匹配表
        int[] next = new int[dest.length()];
        next[0] = 0;
        for (int i = 1, j = 0; i < dest.length(); i++) {
    
    
            //1、当dest.charAt(i) != dest.charAt(j)
            while (j > 0 && dest.charAt(i) != dest.charAt(j)) {
    
    
                j = next[j-1];
            }
            //2、当dest.charAt(i) == dest.charAt(j)
            if (dest.charAt(i) == dest.charAt(j)) {
    
    
                j++;
            }
            next[i] = j;
        }
        return next;
    }
}

猜你喜欢

转载自blog.csdn.net/qq_44682003/article/details/111025487