KMPBC:KMP算法及其改进(kmp with bad character)

news2024/9/26 1:29:10

前言

最近在看字符串匹配算法,突然灵光一闪有了想法,可以把kmp算法时间效率提高,同时保持最坏时间复杂度O(n+m)不变。其中n为主串长度,m为模式串长度,经测试可以块3-10倍,以为发现了新大陆,但是查阅文献后发现已经有了类似了改进。所以发表在CSDN上就算成功!

相关算法

  • KMP 第一个线性时间复杂度的串匹配算法。从左到右对模式串进行匹配,用i指针指向主串,j指针指向模式串。通过next数组快速回退指针j。
  • BM 可看作是KMP的改进,通右到左对模式串匹配,利用坏字符好后缀两个规则来向前移动指针i,大部分时候很快,但是最坏时间复杂度为O(nm)
  • Sunday 比BM更快,从左到右对模式串进行匹配,利用坏字符规则来移动i指针,但是最坏时间复杂度为O(nm)

算法过程

改进后的KMP算法如下:
首先计算next数组,按照kmp一般方法即可。
其次根据sunday算法计算shift数组。
然后用 i i i指向主串s某个字符,j指向模式p中的某个字符,从左到右进行匹配。

  • 如果i和j的字符相等,i,j两个指针同时向右移动。
  • 如果i和j的字符不同,则考虑使用坏字符规则,
    • 如果用坏字符规则能使i变大,则根据规则移动i, 并令j=0。
    • 否则,按照kmp的方法回退指针j。
public int match(String s, String p) {
    int n = s.length();
    int m = p.length();
    int[] next = buildNext(p);    
    int[] shift = buildShift(p);

    int i = 0;
    int j = 0;
    while(i < n && j < m){
        if(s.charAt(i) == p.charAt(j)){
            i++;           // #1
            j++;
        }else{             // #2
            int bad = i - j + m;
            if(bad < n){
              int nexti = bad - shift[s.charAt(bad)] + 1;
              if(nexti > i){
                i = nexti;
                j = 0;
                continue;
              }
            }
            if(j == 0) { // #3
                i++;
            }else{
                j = next[j-1];
            }
        }
    }
    return j == m ? i - m : -1;
  }

算法分析

如上一节代码:算法总体由if部分和else 两部分组成。
除了#3else部分位置无论是否使用坏字符规则都会对j进行回退(即将j减小)。我们考虑j的值是什么时候增加的,显然是在#1的时候。但#1#3执行次数加起来不会超过n, 因此j回退次数也不会超过n。

所以时间复杂度为 T ( n ) ≤ 2 n = O ( n ) T(n) \le 2n = O(n) T(n)2n=O(n)
如果加上预处理buildNext()buildShift()的话, T ( n ) = O ( n + m ) T(n)=O(n+m) T(n)=O(n+m)

实验对比

实验对KMP、BM、Sunday、KMPBC进行了比较,随机生成10000个字符串并随机生成它们的模式串。
四个算法某次运行结果如下,前四行展示了算法的运行时间,最后两个对比了Sunday和KMPBC的比较次数。
在这里插入图片描述
改进后的算法比较次数比sunday更少,且做到了理论上线性。

package org.example;

import java.util.*;

interface IStringMatch {
  int match(String s, String p);
}

class KMP implements IStringMatch {
  
  public static int[] buildNext(String p){
    int n = p.length();
    int[] next = new int[n];  // next[i] 表示 p[0..i] 最长共公前后缀和长度
    int pre = 0; //当前缀长度
    for(int i = 1; i < n; i++){
        if(p.charAt(i) == p.charAt(pre)){
            pre ++;
            next[i] = pre;
        }else{
            if(pre == 0) { // 防上pre-1溢出
                next[i] = 0;
            }else{
                pre = next[pre - 1]; // 在pre之间寻找更小的公共前后缀
                i--; 
            }     
        }
    }
    return next;
  }
  
  public int kmp(String s, String p){
    int n = s.length();
    int m = p.length();
    int[] next = buildNext(p);
    int i = 0;
    int j = 0;
    while(i < n && j < m){
        if(s.charAt(i) == p.charAt(j)){
            i++;
            j++;
        }else{
            if(j == 0) {  // 防上j-1溢出
                i++;
            }else{
                j = next[j-1];
            }
        }
    }
    return j == m ? i - m : -1;
  }

  @Override
  public int match(String s, String p) {
    return kmp(s, p);
  }
}

class BM implements IStringMatch {

  public static int[] buildShift(String p){
    int[] set = new int[256];
    for(int i = 0; i < p.length(); i++){
      set[p.charAt(i)] = (i + 1); // 记录从1开始的位置
    }
    return set;
  }


  @Override
  public int match(String s, String p) {

    // 1. 坏字符规则
    // 2. 好后缀规则
    // 这里直接引用第三方的实现:见附录
    return BMext.indexOf(s.toCharArray(), p.toCharArray());
  }
  
}

class Sunday implements IStringMatch {

  
  static int count = 0;

  @Override
  public int match(String s, String p) {
    int n = s.length();
    int m = p.length();  
    int[] shift = BM.buildShift(p);
    int i = 0;
    int j = 0;
    while(i < n && j < m){
      count ++;
      if(s.charAt(i) == p.charAt(j)){
        i ++;
        j ++;
      }else{
        int bad = i - j + m;
        if(bad < n){
          i += m - (shift[s.charAt(bad)] - 1);
          i -= j;
          j = 0;
        }else{
          return -1;
        }
      }
    }
    return j == m ? i - m : -1;
  }
  
}

class KMPWithBC implements IStringMatch {

  static int count = 0;
  
  @Override
  public int match(String s, String p) {
    int n = s.length();
    int m = p.length();
    int[] next = KMP.buildNext(p);    
    int[] shift = BM.buildShift(p);

    int i = 0;
    int j = 0;
    while(i < n && j < m){
        count ++;
        // 环字符规则加在这也行
        // int bad = i + (m - j) - 1;
        // if(bad < n && shift[s.charAt(bad)] == 0){
        //   i = bad + 1;
        //   j = 0;
        //   continue;
        // }
        if(s.charAt(i) == p.charAt(j)){
            i++;
            j++;
        }else{
            int bad2 = i - j + m;
            if(bad2 < n){
              int nexti = bad2 - shift[s.charAt(bad2)] + 1;
              if(nexti > i){
                i = nexti;
                j = 0;
                continue;
              }
            }
            if(j == 0) {  // 防上j-1溢出
                i++;
            }else{
                j = next[j-1];
            }
        }
    }
    return j == m ? i - m : -1;
  }

}


public class Main{

  
  static Random random=new Random();

  public static String getRandomString(int length){
    String str="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
      StringBuffer sb=new StringBuffer();
      for(int i=0;i<length;i++){
        int number=random.nextInt(5); // 62
        sb.append(str.charAt(number));
      }
      return sb.toString();
  }

  
  public static void main(String[] args){
    int n = 100000;
    String[] words = new String[n];
    String[] patts = new String[n];
    for(int i = 0; i < n; i++){
      String s = getRandomString(10000);
      words[i] = s;
      int pos = random.nextInt(s.length());
      int len = random.nextInt(128) + 1;
      patts[i] = s.substring(pos , Math.min(s.length(), pos + len));
    }

    // algorithms
    IStringMatch[] algs = new IStringMatch[]{
      new KMP(),
      new BM(),
      new Sunday(),
      new KMPWithBC()
    };
    // answers
    int[] ans = new int[n];
    for(int i = 0; i < n; i++){
      ans[i] = words[i].indexOf(patts[i]);
    }
    for(var al : algs){
      Date t1 = new Date();
      for(int i = 0; i < n; i++){
        int an = al.match(words[i], patts[i]);
        if(an != ans[i]) {
          System.out.println(al + ":" + words[i] + " matches " + patts[i] + " eqauls " + ans[i] + " but is" + an);
          System.exit(-1);
        }
      }
      Date t2 = new Date();
      System.out.println(al + ":" + ((t2.getTime() - t1.getTime())) + "ms");  
    }

    System.out.println("sunday: " + Sunday.count);
    System.out.println("kmpbc: " + KMPWithBC.count);
  }
}


附录

BM算法实现

package org.example;

class BMext {

      /**
     * Returns the index within this string of the first occurrence of the
     * specified substring. If it is not a substring, return -1.
     *
     * There is no Galil because it only generates one match.
     *
     * @param haystack The string to be scanned
     * @param needle The target string to search
     * @return The start index of the substring
     */
    public static int indexOf(char[] haystack, char[] needle) {
        if (needle.length == 0) {
            return 0;
        }
        int charTable[] = makeCharTable(needle);
        int offsetTable[] = makeOffsetTable(needle);
        for (int i = needle.length - 1, j; i < haystack.length;) {
            for (j = needle.length - 1; needle[j] == haystack[i]; --i, --j) {
                if (j == 0) {
                    return i;
                }
            }
            // i += needle.length - j; // For naive method
            i += Math.max(offsetTable[needle.length - 1 - j], charTable[haystack[i]]);
        }
        return -1;
    }

    /**
     * Makes the jump table based on the mismatched character information.
     */
    private static int[] makeCharTable(char[] needle) {
        //final int ALPHABET_SIZE = Character.MAX_VALUE + 1; // 65536
        final int ALPHABET_SIZE = 256;
        int[] table = new int[ALPHABET_SIZE];
        for (int i = 0; i < table.length; ++i) {
            table[i] = needle.length;
        }
        for (int i = 0; i < needle.length; ++i) {
            table[needle[i]] = needle.length - 1 - i;
        }
        return table;
    }

    /**
     * Makes the jump table based on the scan offset which mismatch occurs.
     * (bad-character rule).
     */
    private static int[] makeOffsetTable(char[] needle) {
        int[] table = new int[needle.length];
        int lastPrefixPosition = needle.length;
        for (int i = needle.length; i > 0; --i) {
            if (isPrefix(needle, i)) {
                lastPrefixPosition = i;
            }
            table[needle.length - i] = lastPrefixPosition - i + needle.length;
        }
        for (int i = 0; i < needle.length - 1; ++i) {
            int slen = suffixLength(needle, i);
            table[slen] = needle.length - 1 - i + slen;
        }
        return table;
    }

    /**
     * Is needle[p:end] a prefix of needle?
     */
    private static boolean isPrefix(char[] needle, int p) {
        for (int i = p, j = 0; i < needle.length; ++i, ++j) {
            if (needle[i] != needle[j]) {
                return false;
            }
        }
        return true;
    }

    /**
     * Returns the maximum length of the substring ends at p and is a suffix.
     * (good-suffix rule)
     */
    private static int suffixLength(char[] needle, int p) {
        int len = 0;
        for (int i = p, j = needle.length - 1;
                 i >= 0 && needle[i] == needle[j]; --i, --j) {
            len += 1;
        }
        return len;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/889810.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Deep Automatic Natural Image Matting

https://github.com/JizhiziLi/AIM 工程地址 抠图的传统方法通常是基于 trimap&#xff08;三值图&#xff09;对前景、背景和 alpha 进行估计&#xff0c;但当图像中的前景和背景颜色相似或有着比较复杂的纹理时&#xff0c;传统算法很难取得比较好的效果。 自动图像抠图(A…

《华为认证》双机热备份简介

定义 双机热备份&#xff08;Hot-Standby Backup&#xff09;是指&#xff0c;当两台设备在确定主用&#xff08;Master&#xff09;设备和备用&#xff08;Backup&#xff09;设备后&#xff0c;由主用设备进行业务的转发&#xff0c;而备用设备处于监控状态&#xff0c;同时…

windows C++python编译配置

python官网下载windows下的installer安装包&#xff0c;添加到命令行when installing download mingw&#xff0c;选seh&#xff0c;把g的所在目录添加到环境变量 stdc.h 添加到 xxx\mingw64\lib\gcc\x86_64-w64-mingw32\8.1.0\include\bits&#xff0c;gcc的include目录下 …

23款奔驰GLS400升级原厂几何多光束大灯,让智能照亮您前行的路

奔驰几何多光束大灯核心特点就是通过内部的84颗可独立控制的LED光源&#xff0c;行车远光灯会甄别对向驶来的车辆或者行人&#xff0c;并且动态的跟随目标&#xff0c;之后阴影话该区域&#xff0c;避免晃到车辆和行人。

Spring项目使用Redis限制用户登录失败的次数以及暂时锁定用户登录权限

文章目录 背景环境代码实现0. 项目结构图&#xff08;供参考&#xff09;1. 数据库中的表&#xff08;供参考&#xff09;2. 依赖&#xff08;pom.xml&#xff09;3. 配置文件&#xff08;application.yml&#xff09;4. 配置文件&#xff08;application-dev.yml&#xff09;5…

【仿写框架之仿写Tomact】四、封装HttpRequest对象(属性映射http请求报文)、HttpResponse对象(属性映射http响应报文)

文章目录 1、创建HttpRequest对象2、创建HttpResponse对象 1、创建HttpRequest对象 HttpRequest对象中的属性与HTTP协议中的内容对应&#xff0c;用于后序servlet从request中获取请求中的参数。 参照http请求报文&#xff1a; import java.io.BufferedReader; import java…

Spring-Bean的生命周期

目录 生命周期汇总 细分生命周期 1.实例化 2.属性赋值&#xff08;依赖注入&#xff09; 3.Aware接口 4.BeanPostProcessor接口 5.初始化 6.销毁 测试验证 类结构 业务类 测试类 生命周期汇总 Spring Bean 的生命周期见下图 &#xff08;一定记忆好下图&#x…

java Graphics 图片叠放在另一张图片上,生成文字图片(可设置多图一起放到底图上)

直接上代码&#xff1a; public static void createGTImage(GtInfo resultObj) {String backPath resultObj.getBackPath();String enterpriseName resultObj.getEnterpriseName();String gtResultPath resultObj.getGtResultPath();int gtResultPathX resultObj.getGtResu…

具身智能:人工智能的下一个浪潮

原创 | 文 BFT机器人 特斯拉 2023 年股东会上&#xff0c;马斯克强调了人形机器人对特斯拉未来的重要性&#xff0c;并预测其将成为公司的主要长期价值来源。他进一步表示&#xff1a;“如果人形机器人和人的比例大致为2比1&#xff0c;那么人们对机器人的需求可能达到100亿乃…

用二进制来输出一个数

用二进制来输出一个数 1&#xff0c;一个数 #include <stdio.h> #include <stdlib.h> #include <stdint.h>int main() {uint32_t m 0x00C00000;printf("m%o,m%d,m0x%x\n",m,m,m);binary(m);return 0; }2&#xff0c;方法 void binary(uint32_t…

Springboot 实践(7)springboot添加html页面,实现数据库数据的访问

前文讲解&#xff0c;项目已经实现了数据库Dao数据接口&#xff0c;并通过spring security数据实现了对系统资源的保护。本文重点讲解Dao数据接口页面的实现&#xff0c;其中涉及页面导航栏、菜单栏及页面信息栏3各部分。 1、创建html页面 前文讲解中&#xff0c;资源目录已经…

应届生如何快速通过软件测试面试?

应届生&#xff0c;没有实际项目经验怎么破&#xff1f; 面试的过程并不为为了显示面试官技术有多牛&#xff0c;也不是为了体现他们公司有多么难进而是考察你的能力和招聘需求是否相匹配&#xff0c;进而评估你能否满足工作需求&#xff0c;甚至实现更多的岗位期待。 弄清楚…

【【典型电路设计之片内存储器的设计之RAM的Verilog HDL描述一】】

典型电路设计之片内存储器的设计之RAM的Verilog HDL描述一 RAM是随机存储器&#xff0c;存储单元的内容可按需随意取出或存入。这种存储器在断电后将丢失所有数据&#xff0c;一般用来存储一些短时间内使用的程序和数据。 其内部结构如下图所示&#xff1a; 例&#xff1a;用…

C语言如何实现DES加密与解密

C语言实现DES加密解密 #include "des.h" //移位表 static Table_size const shiftTable[NumberOfKeys] {1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1}; //E扩展表 static Table_size const eTable[des_key_pc2_standard]{32, 1, 2, 3, 4, 5, 4, 5, 6, …

学校提升教学质量,这个方法很炸裂!

当今教育领域日益强调教学质量的提升和教师专业发展。为了实现这一目标&#xff0c;许多学校开始采用在线巡课系统来监控、评估和改进教学过程。 在线巡课系统作为一种创新的教学管理工具&#xff0c;不仅有助于教育管理者更好地了解教师的教学实践&#xff0c;还能够为教师提供…

如何发布自己的小程序

小程序的基础内容组件 text&#xff1a; 文本支持长按选中的效果 <text selectable>151535313511</text> rich-text: 把HTML字符串渲染为对应的UI <rich-text nodes"<h1 stylecolor:red;>123</h1>"></rich-text> 小程序的…

国标GB28181视频平台EasyGBS国标平台针对数据库删除级联数据后的无效数据进行优化的具体操作步骤

EasyGBS国标视频云服务可支持通过国标GB28181协议将设备接入&#xff0c;实现视频的实时监控直播、录像、语音对讲、云存储、告警、级联等功能&#xff0c;同时也支持将接入的视频流进行全终端、全平台分发&#xff0c;分发的视频流包括RTSP、RTMP、FLV、HLS、WebRTC等格式。同…

spring核心技术(下)--面向切面aop的特点加案例论证

目录 一.spring的aop的特点 1.1 aop的简介 1.2 举例 二.spring的aop的专业术语 三.论证模拟 3.1.前置通知 3.2.后置通知 3.3.环绕通知 3.4.异常通知 3.5.过滤通知 今天就分享到这啦&#xff01;&#xff01; 一.spring的aop的特点 Spring就是一个轻量级的控制反转(IOC)和面向…

go es实例

go es实例 1、下载第三方库 go get github.com/olivere/elastic下载过程中出现如下报错&#xff1a; 解决方案&#xff1a; 2、示例 import package mainimport ("context""encoding/json""fmt""reflect""time""…

电脑文件删除了可以找回吗?3种文件恢复办法!

在日常工作中&#xff0c;我们经常会需要处理很多的工作文件&#xff0c;但有时候会不小心误删电脑中的文件。 那么&#xff0c;电脑上面的文件被删除了可以找回吗&#xff1f;答案是肯定的&#xff01;下面就来分享三种比较好用的方法&#xff0c;一起来看下吧。 方法一&…