主串:给定的原始字符串

模式串:需要查找的串

例如:需要在字符串 A 中,查找 B 字符串,那么 A 就是主串,B 就是模式串

BF 算法

BF 算法就是暴力匹配算法,挨个去比较,不相等就将整个模式串后移一位,继续比较

KMP 算法

算法的核心思想

假设主串是a,模式串是 b。在模式串与主串匹配的过程中,当遇到不可匹配的字符的时候,我们希望找到一些规律,可以将模式串往后多滑动几位,跳过那些肯定不会匹配的情况,从而避免 BF 算法这种暴力匹配,提高算法性能。下面我们来探讨下这个规律如何找到。

参考下面个主串和模式串的匹配,当模式串移动到当前位置,比对到最后一个字符 D 时,发现与主串不匹配,如果按照 BF 算法,就是把模式串往后移一位,再逐个比较,这样做固然可以,但是效率很差:

一个基本事实是,当 D 与主串不匹配时,我们已知前面的主串序列是 ABCDA,如果把模式串往后移一位肯定和主串不匹配,我们可不可以直接把模式串移到下一个可能和 A 匹配的主串位置?

实际上,KMP 算法正是基于这一理念,设法利用这个已知信息,不把模式串移到已经比较过的位置,继续把它向后移,这样综合下来就极大提高了搜索匹配效率。

怎么找到这个规律,确定把模式串往后移多少位呢?在模式串和主串匹配的过程中,我们把不能匹配的那个字符仍然叫作「坏字符」,把已经匹配的那段字符串叫作「好前缀」:

在模式串和主串匹配的过程中,当遇到坏字符后,对于已经比对过的好前缀,我们只需要拿好前缀本身,在它的后缀子串中,查找最长的那个可以跟好前缀的前缀子串匹配的下标位置,然后将模式串后移到该位置即可。

这里,我们要解释几个概念:

  • 后缀子串:以某个字符串最后一个字符为尾字符的子串(不包含字符串自身),比如上面的 ababa,后缀子串为 baba、aba、ba、a;

  • 前缀子串:以某个字符串第一个字符为首字符的子串(不包含字符串自身),还是以 ababa 为例,前缀子串为 a、aba、abab;

  • 最长可匹配后缀子串:后缀子串与前缀子串最长可匹配子串,也可叫做共有子串,以 ababa 为例,自然是 aba 了,长度为 3;

  • 最长可匹配前缀子串:与上面定义相对,即前缀子串与后缀子串最长可匹配子串。最长可匹配前缀子串和最长可匹配后缀子串肯定是一样的。

假设坏字符所在位置是 j,最长可匹配后缀子串长度为 k,则模式串需要后移的位数为 j-k。每当我们遇到坏字符,就将模式串后移 j-k 位,直到模式串与对应主串字符完全匹配;如果移到最后还是不匹配,则返回 -1。这就是 KMP 算法的核心思想。

KMP 算法的实现

先留个坑

Trie 树的定义、实现及应用

Trie 树的定义

Trie 树,也叫「前缀树」或「字典树」,顾名思义,它是一个树形结构,专门用于处理字符串匹配,用来解决在一组字符串集合中快速查找某个字符串的问题。

注:Trie 这个术语来自于单词「retrieval」,你可以把它读作 tree,也可以读作 try。

Trie 树的本质,就是利用字符串之间的公共前缀,将重复的前缀合并在一起,比如我们有["hello","her","hi","how","seo","so"] 这个字符串集合,可以将其构建成下面这棵 Trie 树:

每个节点表示一个字符串中的字符,从根节点到红色节点的一条路径表示一个字符串(红色节点表示是某个单词的结束字符,但不一定都是叶子节点)。

这样,我们就可以通过遍历这棵树来检索是否存在待匹配的字符串了,比如我们要在这棵 Trie 树中查询 her,只需从 h 开始,依次往下匹配,在子节点中找到 e,然后继续匹配子节点,在 e 的子节点中找到 r,则表示匹配成功,否则匹配失败。通常,我们可以通过 Trie 树来构建敏感词或关键词匹配系统。

Trie 树的实现

Trie 树的复杂度

Trie 树的应用

  1. 敏感词过滤系统

  2. 搜索框联想功能

  3. 进而可以扩展到浏览器网址输入自动补全、IDE代码编辑器自动补全、输入法自动补全功能等。

字符串匹配函数 strstr 底层实现原理剖析

PHP 提供的字符串匹配函数多是单模式匹配,因此大多通过 KMP 算法实现

链接地址:https://xueyuanjun.com/post/21001