字符串匹配算法:从这段代码判断你是不是在大气层

最近刷题刷到leetcode的第28题,原题是这样的:

在这里插入图片描述

这么看来这个貌似好简单

1. 地面层

1.1 算法实现

/*
 *
 * [28] 实现 strStr()
 */
public class Solution {
    public int StrStr(string haystack, string needle) {
        return haystack.IndexOf(needle);
    }
}

1.2 算法结果

在这里插入图片描述

想了想这样不就可以了吗?

对的,是的 这样就可以了,但是我们如果能够做到知其然且知其所以然是不是更可以呢?

2. 对流层(BF-暴力算法与RK算法)

2.1. BF算法

BF算法-Brute Force算法,暴力匹配算法也称简单匹配算法,

2.1.1. BF算法介绍

其基本思路是:

从目标串s=”s0 s1 … sn-1”的第一个字符开始和模式串t=”t0 t1 … tm-1”中的第一个字符比较,若相等,则继续逐个比较后续字符,否则,从目标串s的第2个字符开始重新与模式串t的第一个字符进行比较,依次类推,若从目标串s的第i个字符开始,每个字符依次和模式串t中的对应字符相等,则匹配成功,该算法返回i;否则匹配失败,返回-1。

在这里插入图片描述

2.1.2. BF算法实现

基于上图和上面的基本思路我们可以编写如下代码:


/*
 *
 * [28] 实现 strStr()
 */
public class Solution {
    public int StrStr(string haystack, string needle) {

        if(needle=="")
        {
            return 0;
        }
        for (int i = 0; i < haystack.Length; i++)
        {
            int j = 0;
            for (; j < needle.Length; j++)
            {
                if( i + j < haystack.Length)
                {
                    if(haystack[i+j]!=needle[j])
                    {
                        break;
                    }
                }
                else
                {
                    return -1;
                }
            }
            if(j==needle.Length)
            {
                return i;
            }
        }
        return -1;
    }
}

2.1.3. BF算法缺陷

在这里插入图片描述

在这里插入图片描述

依据上述两张图片中的内容我们可以发现最差时间复杂度会在N*M,主要是由于暴力破解的过程中有非常多的无效比对,而如果我们能够去除算法中的无效比对引导算法找一个正确的再次比对的起始点,那么无疑算法效率将有一个显著的提升。

2.1.4 算法结果

在这里插入图片描述

2.2. RK算法

RK算法的全称叫Rabin-Karp算法,是由它的两位发明者Rabin和Karp的名字来命名的。

2.2.1. RK算法介绍

我在讲BF算法的时候讲过,如果模式串⻓度为m,主串⻓度为n,那在主串中,就会有n-m+1个⻓度为m的⼦串,我们只需要
暴⼒地对⽐这n-m+1个⼦串与模式串,就可以找出主串与模式串匹配的⼦串。

在这里插入图片描述

2.2.2. RK算法实现

/*
 *
 * [28] 实现 strStr()
 */
public class Solution {
    public int StrStr(string haystack, string needle)
    {
        int n = haystack.Length, m = needle.Length;

        if (needle == "")
        {
            return 0;
        }

        Dictionary<string,int> dictionary=new Dictionary<string, int>();
        for (int i = 0; i < n; i++)
        {
            if(i+m>n)
            {
                break;
            }
            string strKey=haystack.Substring(i,m);
            if(!dictionary.ContainsKey(strKey))
            {
                dictionary.Add(strKey,i);
            }
        }

        return dictionary.ContainsKey(needle)?dictionary[needle]:-1;
    }
}

2.2.3. 字典RK算法结果

在这里插入图片描述

3. 平流层(KMP算法)

KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。KMP算法的时间复杂度O(m+n)
在这里插入图片描述

在这里插入图片描述

从上述动图中我们可以看到依据了某种特殊的方式过滤了无效比对,而在整个比对过程中,其实目标主串只是作为目标,作为获取的结果。所以说白了KMP算法是研究模式串的算法。只需要研究当上一个起始节点比对失败之后跳转到第多少个节点进行比对的算法

3.1. 算法思路演算

  1. 所以在标准算法中经常使用next数组来记录,所以next数组是记录模式串的数组信息。
  2. 当计算出next数组,从第一个字符开始比对。
  3. 如果成功,则继续比对下一个字符
  4. 如果失败则计算失败下标next数组值,跳转。继续第三步

3.1.1. next数组中值代表的实际意义

next数组表达当前下标前组成的字符串,其最长真子前缀字符串最长真子后缀字符串相等的最长真子字串的长度

  • 举例说明:

    • 字符串:ACBAC
    • 最长真子前缀字符串:ACBA
    • 最长真子后缀字符串:CBAC
    • 最长真子前缀字符串=最长真子后缀字符串=AC=AC
    • 最长真子字串的长度:AC=2

3.2. 算法代码

/*
 *
 * [28] 实现 strStr()
 */
public class Solution {
    public int StrStr(string haystack, string needle)
    {
        int n = haystack.Length, m = needle.Length;

        if (needle == "")
        {
            return 0;
        }
        int[] next = new int[m];

        InitNextArr(next, needle);

        for (int i = 0, j = 0; i < n; i++)
        {
            while (j > 0 && haystack[i] != needle[j])
            {
                j = next[j - 1];
            }
            if (haystack[i] == needle[j])
            {
                j++;
            }
            if (j == m)
            {
                return i - m + 1;
            }
        }
        return -1;
    }

    private void InitNextArr(int[] next, string needle)
    {
        for (int i = 1, j = 0; i < needle.Length; i++)
        {
            while (j > 0 && needle[i] != needle[j])
            {
                j = next[j - 1];
            }
            if (needle[i] == needle[j])
            {
                j++;
            }
            next[i] = j;
        }
    }
}

3.3. 算法结果

在这里插入图片描述

4. 中间层(BM算法)

具体参考如下:https://zhuanlan.zhihu.com/p/63596339

4.1. 算法介绍

1977年,Robert S.Boyer和J Strother Moore提出了另一种在O(n)时间复杂度内,完成字符串匹配的算法,其在绝大多数场合的性能表现,比KMP算法还要出色

BM算法包含两部分,分别是坏字符规则(bad character rule)好后缀规则(good suffix shift)

其算法是要:可以⼀次性把模式串往后多滑动⼏位,把模式串移动到c的后⾯。那么要将模式串向后滑动多少位呢?,就是使用上述的坏字符规则(bad character rule)好后缀规则(good suffix shift)来确定的,从而保证后移位数的同时不会导致遗漏匹配

在这里插入图片描述

4.2. 算法实现

/*
 * @lc app=leetcode.cn id=28 lang=csharp
 *
 * [28] 实现 strStr()
 */

// @lc code=start
public class Solution {
    
    public int StrStr(string haystack, string needle)
    {
       return BM(haystack.ToCharArray(), haystack.Length, needle.ToCharArray(), needle.Length);
    }

    private static int SIZE = 256; // 全局变量或成员变量

    /// <summary>
    /// 构建坏字符哈希表
    /// </summary>
    /// <param name="b"></param>
    /// <param name="m"></param>
    /// <param name="bc"></param>
    private void GenerateBC(char[] b, int m, int[] bc)
    {
        for (int i = 0; i < SIZE; ++i)
        {
            bc[i] = -1; // 初始化bc
        }
        for (int i = 0; i < m; ++i)
        {
            int ascii = (int)b[i]; // 计算b[i]的ASCII值
            bc[ascii] = i;
        }
    }

    /// <summary>
    /// 构建坏字符哈希表
    /// b表示模式串,m表示⻓度,suffix,prefix数组事先申请好了
    /// </summary>
    /// <param name="b"></param>
    /// <param name="m"></param>
    /// <param name="suffix"></param>
    /// <param name="prefix"></param>
    private void GenerateGS(char[] b, int m, int[] suffix, bool[] prefix)
    {
        for (int i = 0; i < m; ++i)
        { 
            // 初始化
            suffix[i] = -1;
            prefix[i] = false;
        }
        for (int i = 0; i < m - 1; ++i)
        { 
            // b[0, i]
            int j = i;
            int k = 0; // 公共后缀⼦串⻓度
            while (j >= 0 && b[j] == b[m - 1 - k])
            { 
                // 与b[0, m-1]求公共后缀⼦串
                --j;
                ++k;
                suffix[k] = j + 1; //j+1表示公共后缀⼦串在b[0, i]中的起始下标
            }
            if (j == -1)
            {
                prefix[k] = true; //如果公共后缀⼦串也是模式串的前缀⼦串
            }
        }
    }


    /// <summary>
    /// a,b表示主串和模式串;n,m表示主串和模式串的⻓度。
    /// </summary>
    /// <param name="a"></param>
    /// <param name="n"></param>
    /// <param name="b"></param>
    /// <param name="m"></param>
    /// <returns></returns>
    public int BM(char[] a, int n, char[] b, int m)
    {
        int[] bc = new int[SIZE]; // 记录模式串中每个字符最后出现的位置
        GenerateBC(b, m, bc); // 构建坏字符哈希表
        int[] suffix = new int[m];
        bool[] prefix = new bool[m];
        GenerateGS(b, m, suffix, prefix);
        int i = 0; // j表示主串与模式串匹配的第⼀个字符
        while (i <= n - m)
        {
            int j;
            for (j = m - 1; j >= 0; --j)
            { 
                // 模式串从后往前匹配
                if (a[i + j] != b[j]) break; // 坏字符对应模式串中的下标是j
            }
            if (j < 0)
            {
                return i; // 匹配成功,返回主串与模式串第⼀个匹配的字符的位置
            }
            int x = j - bc[(int)a[i + j]];
            int y = 0;
            if (j < m - 1)
            { 
                // 如果有好后缀的话
                y = moveByGS(j, m, suffix, prefix);
            }
            i = i + Math.Max(x, y);
        }
        return -1;
    }

    /// <summary>
    /// j表示坏字符对应的模式串中的字符下标; m表示模式串⻓度
    /// </summary>
    /// <param name="j"></param>
    /// <param name="m"></param>
    /// <param name="suffix"></param>
    /// <param name="prefix"></param>
    /// <returns></returns>
    private int moveByGS(int j, int m, int[] suffix, bool[] prefix)
    {
        int k = m - 1 - j; // 好后缀⻓度
        if (suffix[k] != -1) return j - suffix[k] + 1;
        for (int r = j + 2; r <= m - 1; ++r)
        {
            if (prefix[m - r] == true)
            {
                return r;
            }
        }
        return m;
    }
}
// @lc code=end

4.3. 算法结果

在这里插入图片描述

5. 大气层(见山还是山)

5.1. 对于系统函数而言

在工作中如非必要我们可以直接使用语言自带的IndexOf函数来做

5.2. 对于BF算法而言

  • 第⼀,实际的软件开发中,⼤部分情况下,模式串和主串的⻓度都不会太⻓。⽽且每次模式串与主串中的⼦串匹配的时候,当
    中途遇到不能匹配的字符的时候,就可以就停⽌了,不需要把m个字符都⽐对⼀下。所以,尽管理论上的最坏情况时间复杂度
    是O(n*m),但是,统计意义上,⼤部分情况下,算法执⾏效率要⽐这个⾼很多。

  • 第⼆,朴素字符串匹配算法思想简单,代码实现也⾮常简单。简单意味着不容易出错,如果有bug也容易暴露和修复。在⼯程
    中,在满⾜性能要求的前提下,简单是⾸选。这也是我们常说的KISS(Keep it Simple and Stupid)设计原则。

5.3 辩证的看待问题

一般来说在工作中如非必要我们可以直接使用语言自带的IndexOf函数来做

  • Java源代码实现
/**
     * Code shared by String and StringBuffer to do searches. The
     * source is the character array being searched, and the target
     * is the string being searched for.
     *
     * @param   source       the characters being searched.
     * @param   sourceOffset offset of the source string.
     * @param   sourceCount  count of the source string.
     * @param   target       the characters being searched for.
     * @param   targetOffset offset of the target string.
     * @param   targetCount  count of the target string.
     * @param   fromIndex    the index to begin searching from.
     */
    static int indexOf(char[] source, int sourceOffset, int sourceCount,
            char[] target, int targetOffset, int targetCount,
            int fromIndex) {
    
    
        if (fromIndex >= sourceCount) {
    
    
            return (targetCount == 0 ? sourceCount : -1);
        }
        if (fromIndex < 0) {
    
    
            fromIndex = 0;
        }
        if (targetCount == 0) {
    
    
            return fromIndex;
        }

        char first = target[targetOffset];
        int max = sourceOffset + (sourceCount - targetCount);

        for (int i = sourceOffset + fromIndex; i <= max; i++) {
    
    
            /* Look for first character. */
            if (source[i] != first) {
    
    
                while (++i <= max && source[i] != first);
            }

            /* Found first character, now look at the rest of v2 */
            if (i <= max) {
    
    
                int j = i + 1;
                int end = j + targetCount - 1;
                for (int k = targetOffset + 1; j < end && source[j]
                        == target[k]; j++, k++);

                if (j == end) {
    
    
                    /* Found whole string. */
                    return i - sourceOffset;
                }
            }
        }
        return -1;
    }
  • 构建测试代码

在Java中构建代码,我们将Java源码拿出来,单独运行,跳过Java代码预热,否则就是不讲武德

具体代码可见第6节

private static void initListArr(List<String> listTestInfo, List<String> listTestTarget) {
    
    

       listTestInfo.add("dsadoijsadknsadkjsaifamdsakdpoasdsamdoaksdpoasdas,d;lk");
       listTestInfo.add("dsadoijsadknsadkjsaifamdoaksdpoasdas,d;lk");
       listTestInfo.add("dsadoijsadknsadkjsaifamdsakdpoasddkjsaifamdsakdpoasdsamdoaksdpoasdas,d;lk");
       listTestInfo.add("dsadoijsdkjsaifamdsakdpoasdsamdoaksdpoasdas,d;lk");
       listTestInfo.add("dsadoijsadknsadkjsaifamdsakdpooaksdpoasdas,d;lk");
       listTestInfo.add("dsadoijsadknsadkjsaifamdsakdpoaipoasdsamdoaksdpoaaifamdakdpoasdsamdoaksdpoaasddkjsaifamdsakdpoasdsamdoaksdpoasdas,d;lk");
       listTestInfo.add("dsadoijsadknsadkjsaifamdsakdpoaaaifamdakdpoasdsamdoaksdpoaasddkjsaifamdsakdpoasdsamdoaksdpoasdas,d;lk");
       listTestInfo.add("dsadoijsadknsadkjsaifamdsakdpoaipoasdsamdoaksdpoaaisamdoaksdpoaasddkjsaifamdsakdpoasdsamdoaksdpoasdas,d;lk");
        listTestInfo.add("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab");


       listTestTarget.add("dsadoi");
       listTestTarget.add("sadkjsaifa");
       listTestTarget.add("dpoasdas,d;lk");
       listTestTarget.add("amdsakdpoasdsa;lk");
       listTestTarget.add("aifamdsakdpooas,d;lk");
       listTestTarget.add("dfamdakdpoasdsamdoaksdpo");
       listTestTarget.add("dsifamdakdpoasdsamdoaksdpoaasddkjsaifamdsakdpoasdsamdoaksdpoasdas,d;lk");
       listTestTarget.add("dsadoijsadknsadkjsaifam");
        listTestTarget.add("aaaaaaaaaaaaaaaaab");
    }

运行时间如下

Java自带算法时间:2688
-------------------------------------------------------------------
BM算法时间:3601
-------------------------------------------------------------------
KMP算法时间:2134
private static void initListArr(List<String> listTestInfo, List<String> listTestTarget) {
    
    

//        listTestInfo.add("dsadoijsadknsadkjsaifamdsakdpoasdsamdoaksdpoasdas,d;lk");
//        listTestInfo.add("dsadoijsadknsadkjsaifamdoaksdpoasdas,d;lk");
//        listTestInfo.add("dsadoijsadknsadkjsaifamdsakdpoasddkjsaifamdsakdpoasdsamdoaksdpoasdas,d;lk");
//        listTestInfo.add("dsadoijsdkjsaifamdsakdpoasdsamdoaksdpoasdas,d;lk");
//        listTestInfo.add("dsadoijsadknsadkjsaifamdsakdpooaksdpoasdas,d;lk");
//        listTestInfo.add("dsadoijsadknsadkjsaifamdsakdpoaipoasdsamdoaksdpoaaifamdakdpoasdsamdoaksdpoaasddkjsaifamdsakdpoasdsamdoaksdpoasdas,d;lk");
//        listTestInfo.add("dsadoijsadknsadkjsaifamdsakdpoaaaifamdakdpoasdsamdoaksdpoaasddkjsaifamdsakdpoasdsamdoaksdpoasdas,d;lk");
//        listTestInfo.add("dsadoijsadknsadkjsaifamdsakdpoaipoasdsamdoaksdpoaaisamdoaksdpoaasddkjsaifamdsakdpoasdsamdoaksdpoasdas,d;lk");
        listTestInfo.add("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab");


//        listTestTarget.add("dsadoi");
//        listTestTarget.add("sadkjsaifa");
//        listTestTarget.add("dpoasdas,d;lk");
//        listTestTarget.add("amdsakdpoasdsa;lk");
//        listTestTarget.add("aifamdsakdpooas,d;lk");
//        listTestTarget.add("dfamdakdpoasdsamdoaksdpo");
//        listTestTarget.add("dsifamdakdpoasdsamdoaksdpoaasddkjsaifamdsakdpoasdsamdoaksdpoasdas,d;lk");
//        listTestTarget.add("dsadoijsadknsadkjsaifam");
        listTestTarget.add("aaaaaaaaaaaaaaaaab");
    }
Java自带算法时间:706
-------------------------------------------------------------------
BM算法时间:422
-------------------------------------------------------------------
KMP算法时间:260

如非必要就是用语言自带的函数,如果在效率上有特殊的要求可以自己根据上述算法思想优化

6. 附代码

大气层代码

import org.apache.commons.lang3.time.StopWatch;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @Classname Main
 * @Description 主窗体函数
 * @Date 2021/9/7 15:00
 * @Created by xiaocai
 */
public class Main {
    
    


    private static void initListArr(List<String> listTestInfo, List<String> listTestTarget) {
    
    

//        listTestInfo.add("dsadoijsadknsadkjsaifamdsakdpoasdsamdoaksdpoasdas,d;lk");
//        listTestInfo.add("dsadoijsadknsadkjsaifamdoaksdpoasdas,d;lk");
//        listTestInfo.add("dsadoijsadknsadkjsaifamdsakdpoasddkjsaifamdsakdpoasdsamdoaksdpoasdas,d;lk");
//        listTestInfo.add("dsadoijsdkjsaifamdsakdpoasdsamdoaksdpoasdas,d;lk");
//        listTestInfo.add("dsadoijsadknsadkjsaifamdsakdpooaksdpoasdas,d;lk");
//        listTestInfo.add("dsadoijsadknsadkjsaifamdsakdpoaipoasdsamdoaksdpoaaifamdakdpoasdsamdoaksdpoaasddkjsaifamdsakdpoasdsamdoaksdpoasdas,d;lk");
//        listTestInfo.add("dsadoijsadknsadkjsaifamdsakdpoaaaifamdakdpoasdsamdoaksdpoaasddkjsaifamdsakdpoasdsamdoaksdpoasdas,d;lk");
//        listTestInfo.add("dsadoijsadknsadkjsaifamdsakdpoaipoasdsamdoaksdpoaaisamdoaksdpoaasddkjsaifamdsakdpoasdsamdoaksdpoasdas,d;lk");
        listTestInfo.add("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab");


//        listTestTarget.add("dsadoi");
//        listTestTarget.add("sadkjsaifa");
//        listTestTarget.add("dpoasdas,d;lk");
//        listTestTarget.add("amdsakdpoasdsa;lk");
//        listTestTarget.add("aifamdsakdpooas,d;lk");
//        listTestTarget.add("dfamdakdpoasdsamdoaksdpo");
//        listTestTarget.add("dsifamdakdpoasdsamdoaksdpoaasddkjsaifamdsakdpoasdsamdoaksdpoasdas,d;lk");
//        listTestTarget.add("dsadoijsadknsadkjsaifam");
        listTestTarget.add("aaaaaaaaaaaaaaaaab");
    }

    public static void main(String[] args) {
    
    

        List<String> listTestInfo=new ArrayList<>();
        List<String> listTestTarget=new ArrayList<>();

        initListArr(listTestInfo,listTestTarget);

        Main main=new Main();

        //创建并启动StopWatch
        StopWatch stopwatch = StopWatch.createStarted();
        for (int k = 0; k < 100000; k++) {
    
    
            for (int i = 0; i <listTestInfo.size() ; i++) {
    
    
                for (int j = 0; j <listTestTarget.size() ; j++) {
    
    
                    main.javaIndexOf(listTestInfo.get(i).toCharArray(),0,listTestInfo.get(i).length(),
                            listTestTarget.get(j).toCharArray(),0,listTestTarget.get(j).length(),0);
                }
            }
        }
        stopwatch.stop();
        System.out.println("Java自带算法时间:"+stopwatch.getTime(TimeUnit.MILLISECONDS));

        System.out.println("-------------------------------------------------------------------");

        stopwatch = StopWatch.createStarted();
        for (int k = 0; k < 100000; k++) {
    
    
            for (int i = 0; i <listTestInfo.size() ; i++) {
    
    
                for (int j = 0; j <listTestTarget.size() ; j++) {
    
    
                    main.BM(listTestInfo.get(i).toCharArray(),listTestInfo.get(i).length(),listTestTarget.get(j).toCharArray(),listTestTarget.get(j).length());
                }
            }
        }
        stopwatch.stop();
        System.out.println("BM算法时间:"+stopwatch.getTime(TimeUnit.MILLISECONDS));

        System.out.println("-------------------------------------------------------------------");

        stopwatch = StopWatch.createStarted();
        for (int k = 0; k < 100000; k++) {
    
    
            for (int i = 0; i <listTestInfo.size() ; i++) {
    
    
                for (int j = 0; j <listTestTarget.size() ; j++) {
    
    
                    main.KMP(listTestInfo.get(i).toCharArray(),listTestTarget.get(j).toCharArray());
                }
            }
        }
        stopwatch.stop();
        System.out.println("KMP算法时间:"+stopwatch.getTime(TimeUnit.MILLISECONDS));
    }

    /**
     * Code shared by String and StringBuffer to do searches. The
     * source is the character array being searched, and the target
     * is the string being searched for.
     *
     * @param   source       the characters being searched.
     * @param   sourceOffset offset of the source string.
     * @param   sourceCount  count of the source string.
     * @param   target       the characters being searched for.
     * @param   targetOffset offset of the target string.
     * @param   targetCount  count of the target string.
     * @param   fromIndex    the index to begin searching from.
     */
    int javaIndexOf(char[] source, int sourceOffset, int sourceCount,
                       char[] target, int targetOffset, int targetCount,
                       int fromIndex) {
    
    
        if (fromIndex >= sourceCount) {
    
    
            return (targetCount == 0 ? sourceCount : -1);
        }
        if (fromIndex < 0) {
    
    
            fromIndex = 0;
        }
        if (targetCount == 0) {
    
    
            return fromIndex;
        }

        char first = target[targetOffset];
        int max = sourceOffset + (sourceCount - targetCount);

        for (int i = sourceOffset + fromIndex; i <= max; i++) {
    
    
            /* Look for first character. */
            if (source[i] != first) {
    
    
                while (++i <= max && source[i] != first);
            }

            /* Found first character, now look at the rest of v2 */
            if (i <= max) {
    
    
                int j = i + 1;
                int end = j + targetCount - 1;
                for (int k = targetOffset + 1; j < end && source[j]
                        == target[k]; j++, k++);

                if (j == end) {
    
    
                    /* Found whole string. */
                    return i - sourceOffset;
                }
            }
        }
        return -1;
    }

    private static int SIZE = 256; // 全局变量或成员变量

    /// <summary>
    /// 构建坏字符哈希表
    /// </summary>
    /// <param name="b"></param>
    /// <param name="m"></param>
    /// <param name="bc"></param>
    private void GenerateBC(char[] b, int m, int[] bc)
    {
    
    
        for (int i = 0; i < SIZE; ++i)
        {
    
    
            bc[i] = -1; // 初始化bc
        }
        for (int i = 0; i < m; ++i)
        {
    
    
            int ascii = (int)b[i]; // 计算b[i]的ASCII值
            bc[ascii] = i;
        }
    }

    /// <summary>
    /// 构建坏字符哈希表
    /// b表示模式串,m表示⻓度,suffix,prefix数组事先申请好了
    /// </summary>
    /// <param name="b"></param>
    /// <param name="m"></param>
    /// <param name="suffix"></param>
    /// <param name="prefix"></param>
    private void GenerateGS(char[] b, int m, int[] suffix, boolean[] prefix)
    {
    
    
        for (int i = 0; i < m; ++i)
        {
    
    
            // 初始化
            suffix[i] = -1;
            prefix[i] = false;
        }
        for (int i = 0; i < m - 1; ++i)
        {
    
    
            // b[0, i]
            int j = i;
            int k = 0; // 公共后缀⼦串⻓度
            while (j >= 0 && b[j] == b[m - 1 - k])
            {
    
    
                // 与b[0, m-1]求公共后缀⼦串
                --j;
                ++k;
                suffix[k] = j + 1; //j+1表示公共后缀⼦串在b[0, i]中的起始下标
            }
            if (j == -1)
            {
    
    
                prefix[k] = true; //如果公共后缀⼦串也是模式串的前缀⼦串
            }
        }
    }

    /// <summary>
    /// a,b表示主串和模式串;n,m表示主串和模式串的⻓度。
    /// </summary>
    /// <param name="a"></param>
    /// <param name="n"></param>
    /// <param name="b"></param>
    /// <param name="m"></param>
    /// <returns></returns>
    public int BM(char[] a, int n, char[] b, int m)
    {
    
    
        int[] bc = new int[SIZE]; // 记录模式串中每个字符最后出现的位置
        GenerateBC(b, m, bc); // 构建坏字符哈希表
        int[] suffix = new int[m];
        boolean[] prefix = new boolean[m];
        GenerateGS(b, m, suffix, prefix);
        int i = 0; // j表示主串与模式串匹配的第⼀个字符
        while (i <= n - m)
        {
    
    
            int j;
            for (j = m - 1; j >= 0; --j)
            {
    
    
                // 模式串从后往前匹配
                if (a[i + j] != b[j]) break; // 坏字符对应模式串中的下标是j
            }
            if (j < 0)
            {
    
    
                return i; // 匹配成功,返回主串与模式串第⼀个匹配的字符的位置
            }
            int x = j - bc[(int)a[i + j]];
            int y = 0;
            if (j < m - 1)
            {
    
    
                // 如果有好后缀的话
                y = moveByGS(j, m, suffix, prefix);
            }
            i = i + Math.max(x, y);
        }
        return -1;
    }

    /// <summary>
    /// j表示坏字符对应的模式串中的字符下标; m表示模式串⻓度
    /// </summary>
    /// <param name="j"></param>
    /// <param name="m"></param>
    /// <param name="suffix"></param>
    /// <param name="prefix"></param>
    /// <returns></returns>
    private int moveByGS(int j, int m, int[] suffix, boolean[] prefix)
    {
    
    
        int k = m - 1 - j; // 好后缀⻓度
        if (suffix[k] != -1) return j - suffix[k] + 1;
        for (int r = j + 2; r <= m - 1; ++r)
        {
    
    
            if (prefix[m - r] == true)
            {
    
    
                return r;
            }
        }
        return m;
    }
    public int KMP(char[] haystack, char[] needle)
    {
    
    
        int n = haystack.length, m = needle.length;

        if (needle.length==0)
        {
    
    
            return 0;
        }
        int[] next = new int[m];

        InitNextArr(next, needle);

        for (int i = 0, j = 0; i < n; i++)
        {
    
    
            while (j > 0 && haystack[i]!= needle[j])
            {
    
    
                j = next[j - 1];
            }
            if (haystack[i] == needle[j])
            {
    
    
                j++;
            }
            if (j == m)
            {
    
    
                return i - m + 1;
            }
        }
        return -1;
    }

    private void InitNextArr(int[] next, char[] needle)
    {
    
    
        for (int i = 1, j = 0; i < needle.length; i++)
        {
    
    
            while (j > 0 && needle[i] != needle[j])
            {
    
    
                j = next[j - 1];
            }
            if (needle[i] == needle[j])
            {
    
    
                j++;
            }
            next[i] = j;
        }
    }
}

7. 参考链接

https://zhuanlan.zhihu.com/p/63596339

https://www.zhihu.com/question/21923021/answer/1825831303

    {
        while (j > 0 && haystack[i]!= needle[j])
        {
            j = next[j - 1];
        }
        if (haystack[i] == needle[j])
        {
            j++;
        }
        if (j == m)
        {
            return i - m + 1;
        }
    }
    return -1;
}

private void InitNextArr(int[] next, char[] needle)
{
    for (int i = 1, j = 0; i < needle.length; i++)
    {
        while (j > 0 && needle[i] != needle[j])
        {
            j = next[j - 1];
        }
        if (needle[i] == needle[j])
        {
            j++;
        }
        next[i] = j;
    }
}

}


# 7. 参考链接

https://zhuanlan.zhihu.com/p/63596339

https://www.zhihu.com/question/21923021/answer/1825831303

https://leetcode-cn.com/problems/implement-strstr/solution/shi-xian-strstr-by-leetcode-solution-ds6y/

猜你喜欢

转载自blog.csdn.net/a13407142317/article/details/120167058