1. 前言

看了阮一峰的字符串匹配的KMP算法,写得很好,推荐看看。

不过我想自己写个例子描述一下这个算法,顺便写个PHP实现,于是有了这篇博文。

2. 概述 [来自维基百科]

字符串搜索算法

字符串搜索算法(String searching algorithms)又称字符串比对算法(string matching algorithms)是一种搜索算法,是字符串算法中的一类,用以试图在一长字符串或文章中,找出其是否包含某一个或多个字符串,以及其位置。

部分算法比较 [克努斯-莫里斯-普拉特算法即KMP算法]

令 m 为模式的长度, n 为要搜索的字符串长度, k为字母表长度。

image.png

3. 算法解读

3.1 例子

给定字符串:

RXYZAHXFXYZAXYZAXYZ

需要搜索的字符串是:

XYZAXY

朴素匹配的步骤很简单,先对比两个字符串的第一个字母,如果不一样,对比给定字符串的下一个字母,如果一样,那么对比两个字符串的第二个字母,以此类推。这种算法的缺点是效率低,因为做了很多无用工,比如,我在匹对 RXYZAHXFXYZAXYZAXYZ和XYZAXY字符串的时候,我在匹对给定字符串的XYZAH和要搜索的字符串XYZAXY这一步,前四个字母的已经匹对成功的,这四个字母是

XYZA

这个字符串的前缀和后缀

前缀:

[X,XY,XYZ]

后缀:

[YZA,ZA,A]

没有共同的元素,这表示在这个长度内,不会有能和要搜索字符串的前缀匹配的部分,那么就可以直接跳过这一部分字符串的对比。这就是KMP算法的核心。所以,我们首先要对要搜索的字符串生成一个匹配表。

3.2 部分匹配表

如何给字符串生成一个匹配表?

XYZAXY

- X 前缀和后缀都是空,所以共有元素的长度0;

- XY 前缀[X],后缀[Y], 共有元素的长度0;

- XYZ 前缀[X,XY],后缀[Y,YZ],共有元素的长度0;

- XYZA 前缀[X,XY,XYZ],后缀[YZA,ZA,A],共有元素的长度0;

- XYZAX 前缀[X,XY,XYZ,XYZA],后缀[YZAX,ZAX,AX,X],共有元素X的长度1;

- XYZAXY 前缀[X,XY,XYZ,XYZA,XYZAX],后缀[YZAXY,ZAXY,AXY,XY,Y],共有元素XY的长度2;

因此生成的匹配表:

image.png

3.3 匹配

好了,现在来看一下怎么使用上一步骤的表。

首先,移位的计算公式:

移动位数 = 已匹配的字符数 - 对应的部分匹配值

下面看具体步骤:

步骤1

image.png

匹配第一个字符,不一致,则将要搜索的字符串右移一位,直至到匹配第1位字母。

步骤2

image.png

从第一个字符串的第一个X开始,已匹配是4位,查表最后一位匹配的字母对应的匹配值是0,所以右移4位。

步骤3

image.png

移位后发现不匹配,又要继续右移一位,直至匹配第1位字母。

步骤4

image.png

发现匹配了X之后,下一位右不匹配了,查表得到X对应的匹配值是0,1-0=1,再右移一位。

步骤5

image.png

继续匹配,666,发现完全匹配,所以呀,就是找到了一个出现的地方。这时候,已匹配是6个,查表部分匹配值2,移动位数6-2=4.

步骤6

image.png

又继续匹配,发现又发现一个。这时候移位位数是4,给定字符串已经没了,所以匹配终止。

3.4 结论

要搜索的字符串在给定字符串出现的次数是2次,如图:

image.png

4. 伪代码[来自《算法导论》]

预处理,生成部分匹配表

//注意:伪代码下标都是从1开始,这是《算法导论》约定俗成的

COMPUTE-PREFIX-FUNCTION(P)

m = P.length

let π[1...m] be a new array

π[1] = 0

k = 0

for q = 2 to m

while k > 0 and P[k +1] != P[q] //这一步很巧妙,但是比较难理解,下面解释

k = π[k]

if P[k + 1] == P[q] //对比上一个部分匹配值的下一个字母是否与当前字母一致

k = k + 1

π[q] = k //保存q对于的部分匹配值

return π

下面解释上面伪代码COMPUTE-PREFIX-FUNCTION的关键步骤

while k > 0 and P[k +1] != P[q]

k = π[k]

举个比较好理解的例子:

P = XYXYXZT

假设当前运行到Z,则之前生成的 π为

π[1~5] = [0,0,1,2,3];

此时 k = 3, q = 6, P[4] != P[6] 符合while循环的条件,进入循环。

1.P[4]= P[6]:

首先,在这里为什么要比较P[4] 和 P[6]?这是因为Z前面X对于的π[5] = 3,

这说明P[1~3] = P[3~5],所以一旦P[4]= P[6],则立马可以

进行下一步的k=k+1(这时if的判断结果肯定的true);

2.如果P[4] != P[6]:

这时候我们把注意力放在P[1~3],这时候我们求的是P[1~3]前缀和P[4~6]的后缀的公共元素。

这时候我们获取P[3]的部分匹配值,π[3]=1,说明P[1] = P[3]。由上面的P[1~3] = P[3~5]知道,

P[1] = P[3] = P[5],那么我们只需比较P[2] = P[6],无需从头匹配一遍P[1~3]=P[4~6],假设相等,

这时候就有P[1~2] = P[5~6],对应的部分匹配值+1,但是,很不幸,这时候依然不等,所以继续循环。

3. 结论:

k = π[k]其实是利用了部分匹配值提供的信息减少比较次数。

匹配

KMP-MATCHER(T, P)

n = T.length

m = P.length

π = COMPUTE-PREFIX-FUNCTION(P)

q = 0

for i = 1 to n

while q > 0 and P[q + 1] != T[i] //这一步和上面的那一步本质上意义是相同的

q = π[q]

if P[q + 1] == T[i]

q = q + 1

if q == m

print "Pattern occurs with shift" i - m

q = π[q]

运行时间分析

运行摊还分析的聚合方法进行分析,过程COMPUTE-PREFIX-FUNCTION的运行时间为Θ(m)。唯一微妙的部分是while循环总共执行时间是O(m)。下面将说明它至多进行了m-1次迭代。我们从观察k的值开始:

1)初始值0,并且增加k的唯一方法是

if P[k + 1] == P[q]

k = k + 1

这个在for循环每次迭代至多执行一次,因此,k总共增加m-1次;

2) 在进行for循环时,k

3)k永远不可能为负值。

综上,k的递减来自于while循环,它由k在所有for循环迭代中的增长所限定,k总共下降m-1。因此,while循环最多迭代m-1次,并且COMPUTE-PREFIX-FUNCTION的运行时间为Θ(m)。

同样的方法分析可知KMP-MATCHER的时间复杂度是Θ(n)。

5. PHP实现

class Kmp {

/**

* 生成部分匹配表

* @param string $p

* @return array

*/

public function ComputePrefix($p)

{

$m = strlen($p);

$table = [];

$table[0] = 0;

$k = 0;

for($q = 1; $q < $m; $q++)

{

while ($k > 0 && $p[$k] != $p[$q])

$k = $table[$k];

if($p[$k] == $p[$q])

$k = $k + 1;

$table[$q] = $k;

}

return $table;

}

/**

* 匹配

* @param string $str

* @param string $p

*/

public function Matcher($T, $p)

{

$n = strlen($T);

$m = strlen($p);

$table = $this->ComputePrefix($p);

$q = 0;

$match = [];

for($i = 0; $i < $n; $i++)

{

while ($q > 0 && $p[$q] != $T[$i])

$q = $table[$q];

if($p[$q] == $T[$i])

$q = $q + 1;

if($q == $m) {

$match[] = ['begin' => $i - $m + 1, 'end' => $i];

$q = $table[$q - 1];

}

}

return $match;

}

}

$kmp = new Kmp();

$match = $kmp->Matcher('RXYZAHXFXYZAXYZAXYZ', 'XYZAXY');

运行结果:

image.png

即匹配结果有两个,分别是

$p[8] ~ $p[13]

$p[12] ~ $p[17]

php随机匹配算法,字符串匹配的KMP算法+PHP实现相关推荐

  1. [算法系列之二十六]字符串匹配之KMP算法

    一 简介 KMP算法是一种改进的字符串匹配算法,由D.E.Knuth与V.R.Pratt和J.H.Morris同时发现,因此人们称它为克努特-莫里斯-普拉特操作(简称KMP算法).KMP算法的关键是利 ...

  2. 字符串匹配(KMP 算法 含代码)

    主要是针对字符串的匹配算法进行解说 有关字符串的基本知识 传统的串匹配法 模式匹配的一种改进算法KMP算法 网上一比較易懂的解说 小样例 1计算next 2计算nextval 代码 有关字符串的基本知 ...

  3. 算法串匹配C++实现字符串匹配的KMP算法

    新手发帖,很多方面都是刚入门,有错误的地方请大家见谅,迎欢批评指正 之前看<算法导论>符字串匹配分部讲到KMP算法,被弄得云里雾里.天今看到阮一峰写了一篇博客<符字串匹配的KMP算法 ...

  4. Java 字符串匹配的KMP算法

    package Four;import java.util.Scanner;/**** 字符型的kmp算法* @author bai* 描述:* 给你一个文本串,再给你一个模式串,* 文本串中有多少个 ...

  5. 三十五、字符串匹配问题--KMP算法

    一.暴力匹配算法实现字符串匹配 如果用暴力匹配的思路,并假设现在 str1 匹配到 i 位置,子串 str2 匹配到 j 位置,则有: 如果当前字符匹配成功(即 str1[i] == str2[j]) ...

  6. 字符串匹配之KMP算法详解

    kmp算法又称"看毛片"算法,是一个效率非常高的字符串匹配算法.不过由于其难以理解,所以在很长的一段时间内一直没有搞懂.虽然网上有很多资料,但是鲜见好的博客能简单明了地将其讲清楚. ...

  7. 字符串匹配的KMP算法(转)

    转自:http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html 字符串匹配是计算 ...

  8. 字符串匹配问题 ----- KMP算法

    题意: 任意给定一段字符串str("123abc123abc00abc") 再输入一个关键字key("abc") 要求返回str中包含key的所有子串的头下标 ...

  9. 【转载】字符串匹配的KMP算法

    转自:http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html 其他参考:htt ...

最新文章

  1. 实战:使用 Mask-RCNN 的停车位检测
  2. exit命令的返回值
  3. 网页设计精粹:网页中那些迷人的按钮设计
  4. Verilog功能模块——符号位扩展
  5. python爬虫--如何爬取翻页url不变的网站
  6. CSS样式表的规划与组织
  7. 1073. Pearls
  8. 什么是激光雷达技术?
  9. 基于逻辑回归算法模型搭建思路
  10. JavaScript调用Linux系统命令
  11. 《杂记》- 之- 使用windows终端命令查看文件的MD5
  12. 商淘软件S2B2C供应链系统 支持多种电商模式
  13. Filter vs Listener
  14. java excelhandle oschina,基于alibab的easyexcel进行excel表的导出(可自定义handler去设计excel格式)...
  15. 一本通1325:【例7.4】 循环比赛日程表
  16. 项目系统设计和数据库设计(追光的人)
  17. 基于安卓的高清语音技术亮相中国国际通信展览会
  18. win10 屏幕保护时间到了不触发_你真的了解Win10么?网友教你玩转Win10!
  19. 上海的211大学中计算机,上海有哪些211大学
  20. 大数据处理算法--Bloom Filter布隆过滤

热门文章

  1. vue单个表单的校验清空
  2. eCryptfs源码注释(2)
  3. 关于java的项目部署以及需要注意的地方
  4. The Sandbox 与育碧达成合作,疯狂兔子闯入元宇宙
  5. 远程桌面连接无法显示本地磁盘终极解决
  6. Google Adwords新手快速入门教程
  7. Word2010中自动尾注添加参考文献
  8. PHP 常见的数据加密技术
  9. ubuntu 下实现 quagga镜像
  10. UVa11400 Lighting System Design