字符串匹配算法从indexOf函数讲起

简介: 字符串匹配算法从indexOf函数讲起

前言

相信每个学习过Java的人都使用过indexOf函数,indexOf函数我们可以查找一个字符串(模式串)是否在另一个字符串(主串)出现过,返回结果表示出现位置的下标,如果返回-1,表示模式串在主串中不存在,那么,你可曾想过这些查找函数又是如何实现的呢?

image.gif

从indexOf源码看起

首先我们先来看一下indexOf的源码,indexOf的使用方式比较多,这是我们以一个形参的为例。

static String mainString = "Hello my name is HuangLinqing";
static String patternString = "HuangLinqing";
public static void main(String[] args) {
    System.out.printf(mainString.indexOf(patternString, 0) + "");
}

image.gif

运行上面代码的结果,返回的结果是17,说明模式串在主串中存在,并且第一次出现的位置下标是17

indexOf方法最终会走到下面方法中,源码如下所示:

/**
 * 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;
}

image.gif

代码行数不多,接下来我们来分析一下,上面的代码,fromIndex默认是0,target是模式串,targetCount是模式串的大小,source是主串,sourceCount是主串的大小

if (fromIndex >= sourceCount) {
    return (targetCount == 0 ? sourceCount : -1);
}
if (fromIndex < 0) {
    fromIndex = 0;
}
if (targetCount == 0) {
    return fromIndex;
}

image.gif

如果开始查找的位置大于主串的大小,如果模式串是空串就返回主串的大小,否则返回-1,如果模式串的大小等于0就是开始查找的位置,这几行代码很好理解,就不举例子了,主要是下面的代码:

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;
        }
    }
}

image.gif

indexOf底层使用的方法是典型的BF算法,我们先来简单介绍BF算法,再回过头来理解上面的代码就比较容易了

BF与RK算法

    • BF算法

    BF算法就是Brute Force,暴力匹配算法,也成为朴素匹配算法,主串的大小是sourceSize,模式串的大小是targetSize,因为我们要在主串中查找模式串,所以sourceZize > targetSize,所以从主串下标为0开始,连续查找targetSize个字符,再从下标为1开始后,一直到,下标为sourceSize - targetSize ,举个简单的例子在ABCDEFG中查找EF:

    image.gif

    上图依次表示从i为0,到i为4时的依次比较,从图中我们也可以看出,BF算法是比较耗时的,因为比较的次数较多,但是实际比较的时候主串和模式串都不会太长,所以这种比较的方法更容易使用。

    现在我们回过头看看indexOf的下半部分源码,我相信其实不用解释了。

      • RK算法

      RK算法其实就是对BF算法的升级,还是以上面的图为例,在ABCDEFG中查找EF的时候,比如下标为0的时候,我们去比较A和E的值,不相等就不继续往下比较了,但是比如我们现在查找CDF是否在主串中存在,我们要从C已知比较大E发现第三位不相等,这样当模式串前一部分等于主串,只有最后一位不相等的时候,比较的次数太多了,效率比较低,所以我们可以采用哈希计算来比较,哈希计算 后面我会补充一篇。

      我们要将模式串和sourceSize - targetSize + 1 个字符串相比,我们可以先将sourceSize - targetSize + 1个模式串进行哈希计算。与哈希计算后的模式串相比较,如果相等则存在,对于哈希冲突在一般实现中概率比较低,不放心的话我们可以在哈希值相等时候再比较一次原字符串确保准确,哈希的冲突概率也和哈希算法的本身设计有关。这样的话,我们首先计算AB的哈希值 与 模式串的相比较,然后计算BC的哈希值与模式串相比较,直到比较出相等的返回下标即可。

      目录
      相关文章
      |
      3月前
      |
      算法
      【算法】滑动窗口——找到字符串中所有字母异位词
      【算法】滑动窗口——找到字符串中所有字母异位词
      |
      1月前
      |
      算法
      两个字符串匹配出最长公共子序列算法
      本文介绍了最长公共子序列(LCS)问题的算法实现,通过动态规划方法求解两个字符串的最长公共子序列,并提供了具体的编程实现细节和示例。
      80 1
      两个字符串匹配出最长公共子序列算法
      |
      2月前
      |
      XML JavaScript 前端开发
      学习react基础(1)_虚拟dom、diff算法、函数和class创建组件
      本文介绍了React的核心概念,包括虚拟DOM、Diff算法以及如何通过函数和类创建React组件。
      29 2
      |
      3月前
      |
      算法 Java
      掌握算法学习之字符串经典用法
      文章总结了字符串在算法领域的经典用法,特别是通过双指针法来实现字符串的反转操作,并提供了LeetCode上相关题目的Java代码实现,强调了掌握这些技巧对于提升算法思维的重要性。
      |
      3月前
      |
      算法
      【Azure Developer】完成算法第4版书中,第一节基础编码中的数组函数 histogrm()
      【Azure Developer】完成算法第4版书中,第一节基础编码中的数组函数 histogrm()
      |
      4月前
      |
      自然语言处理 算法 搜索推荐
      字符串相似度算法完全指南:编辑、令牌与序列三类算法的全面解析与深入分析
      在自然语言处理领域,人们经常需要比较字符串,这些字符串可能是单词、句子、段落甚至是整个文档。如何快速判断两个单词或句子是否相似,或者相似度是好还是差。这类似于我们使用手机打错一个词,但手机会建议正确的词来修正它,那么这种如何判断字符串相似度呢?本文将详细介绍这个问题。
      273 1
      |
      4月前
      |
      数据采集 算法 JavaScript
      揭开JavaScript字符串搜索的秘密:indexOf、includes与KMP算法
      JavaScript字符串搜索涵盖`indexOf`、`includes`及KMP算法。`indexOf`返回子字符串位置,`includes`检查是否包含子字符串。KMP是高效的搜索算法,尤其适合长模式匹配。示例展示了如何在数据采集(如网页爬虫)中使用这些方法,结合代理IP进行安全搜索。代码示例中,搜索百度新闻结果并检测是否含有特定字符串。学习这些技术能提升编程效率和性能。
      118 1
      揭开JavaScript字符串搜索的秘密:indexOf、includes与KMP算法
      |
      3月前
      |
      算法 C++
      惊爆!KPM算法背后的秘密武器:一行代码揭秘字符串最小周期的终极奥义,让你秒变编程界周期大师!
      【8月更文挑战第4天】字符串最小周期问题旨在找出字符串中最短重复子串的长度。KPM(实为KMP,Knuth-Morris-Pratt)算法,虽主要用于字符串匹配,但其生成的前缀函数(next数组)也可用于求解最小周期。核心思想是构建LPS数组,记录模式串中每个位置的最长相等前后缀长度。对于长度为n的字符串S,其最小周期T可通过公式ans = n - LPS[n-1]求得。通过分析周期字符串的特性,可证明该方法的有效性。提供的C++示例代码展示了如何计算给定字符串的最小周期,体现了KPM算法在解决此类问题上的高效性。
      82 0
      |
      5月前
      |
      算法 Java C语言
      Java中的算法与C语言中的函数
      Java中的算法与C语言中的函数
      41 2
      |
      5月前
      |
      算法 调度 C++
      【调度算法】共享函数vs拥挤距离
      【调度算法】共享函数vs拥挤距离
      71 1