字符串模式匹配:

给定字符串,要求在该字符串(主串)中找到所有匹配一个模式串的子串(一般是返回子串在字符串中的开头位置)。这里把问题简化一下--在该字符串中找到第一个匹配对应模式串的子串即可。要找出剩下的匹配子串,只需往后沿用相同的算法。

问题进一步精炼:给定主串S、模式串P,主串S中的一个索引pos,要求使用一种算法,找到主串S的从索引pos开始的,第一个匹配模式串P的子串。如果能找到,则返回子串的开始位置在S中的索引。


朴素的模式匹配算法:

在进入正题之前,先引入最朴素的模式匹配算法--从S的第pos个字符串起,与P的第一个字符进行比较。若相同,则继续往后比较。若碰到不相同的字符,则从S的第pos+1个字符串起,和P的第一个字符进行比较...... 直至有一个S的子串和P匹配成功或找不到这样的子串。算法用C++代码描述:

朴素模式匹配:

int find(string s , string p , int pos) {int lenS = s.length(), lenP = p.length();if(lenP > lenS-pos)return -1; int i = pos, j = 0;while(i < lenS && j < lenP){if(s[i] == p[j]){i ++;j ++;}else{i = i-j+1;j = 0;}}if(j == lenP)return i-lenP;return -1;
}

设串S的长度为n,串P的长度为m,则算法的时间复杂度为O(nm)

仔细观察,不知道你有没有发现这个算法运行过程中,有许多冗余的比较?假设匹配发展到如下情况:

索引:      0 1 2 3 4 5 6 7 8 9

S:         a b a b c a b c a c

P:               a b c a c

索引:          0 1 2 3 4

 情况1

这时,我们发现 S[6] != P[4]。按照上面的算法,下一步就是要重新将S[3] 和 P[0] 对准,重新进行比较。S的指针从 i = 6 回溯到 i = 3,如下所示:

索引:      0 1 2 3 4 5 6 7 8 9

S:         a b a b c a b c a c

P:                  a b c a c

索引:             0 1 2 3 4

情况2

其实,经过我们观察就可以发现,后续的比较 i = 3,j = 0 和 i = 4,j = 0 和 i = 5,j = 0 都是没有必要进行的。遇到情况1,我们直接将模式向右滑动3个字符,跳到情况3进行比较即可。此时,S 的指针仍然为 i = 6,不用回溯。

索引:      0 1 2 3 4 5 6 7 8 9

S:         a b a b c a b c a c

P:                        a b c a c

索引:             0 1 2 3 4

情况3

      以上说明,我们可以通过利用前面的比较过的信息,当匹配出现字符不等的时候,向后跳跃,不做无用的比较 ! KMP算法说,"让我们来一起跳跃吧 ~!"


KMP算法:

前期分析:

KMP算法,是用于解决模式匹配问题的快速算法,由D.E.Knuth、V.R.Pratt和J.J.Morris同时发现,也因此而得名。这个算法非常直观,推导过程简洁优美。

讨论一般情况,设主串:,模式串:

为改进算法,当匹配过程中产生失配()时,设主串S的第i个字符应与模式中的第k(k < j)个字符继续比较,则模式串P中的索引 k 前面长度为k-1的子串必与主串的指针 i 前面长度为 k-1 的子串匹配。则有:

 (1)

回到失配的情况,虽然,但是主串S的指针 i 前面的长度为 k-1 的子串和模式串P的指针 j 前面的长度为 k-1 的子串必相匹配。则有:

(2)

由(1)、(2)可得:(3)

至此,我们发现 k 的选择竟只和模式串P相关

总结一下前面的信息,这个改进的算法中主串S的指针 i 无回溯,模式串P的指针 j 可能需要反复回溯。那么算法快速的关键就在于--i 无回溯,j 尽可能地少回溯。当 k(k < j) 越大的时候,它离 j 越近,j 的回溯步长就越短。

因此,我们要找的 k 就是满足式(3)的最大 k,即  (4)

next 数组的定义:

令next[ j ] = k,k 表示当模式中第 j 个字符与主串中第 i 个字符 “失配” 时,需要滑动模式串(回溯 j 指针),让模式串的第 k 个字符和主串中的第 i 个字符对齐,继续匹配(从这两个字符开始继续匹配,尚不知相不相等)。

综上,我们已经可以给出KMP算法的框架

//pos从1开始算起, 字符串的索引从0开始算起
int KMP(string s , string p , int pos) {int i = pos, j = 1;int lenP = p.length(), lenS = s.length();while( i <= lenS && j <= lenP ){if( j == 0 || s[i-1] == p[j-1]){++ i;++ j;}elsej = next[j];}if(j > lenP)return i - lenP;return -1;
}

求next数组:

1. 当 j = 1时,令next[ j ] = 0,直观上表示模式串P的第0位和主串的第 i 位比较,相当于让模式串的第1位和主串的第 i+1 位比较。

2. 当 j 不等于1时,若集合不空,则令

next[ j ] = 

3. 其他情况下,令next[ j ] = 1,就是让模式串的第1位和主串的第 i 位进行比较。

综上,next函数的定义为:

 (5)

现如今,问题的关键在于求 ,我们通过分析发现可以使用递推的方法来求得这个值。

设next[ 1 ~ j ]已知,要求 next[ j+1 ] = 使得,是满足 的最大值。

由等式(3)可知,问题被转化为模式串P自身的模式匹配问题:当 “第二个串P” 的第 j+1 位与 “第一个串P” 的第 i +1 位失配的时候,应当移动 “第二个串P” 使得其第 next[ j + 1] 位与 “第一个串P”的第 i+1 位对齐,继续匹配。

令 k =  = next[ j ]( k = ,表示第一代k),则:

等式 (1 < k < j)成立,且不存在  (),使得这个等式也成立。(6)

1)  若,则进一步有 (7),且不可能存在 )也满足(7),则next [ j+1 ] =  + 1,即 next [ j+1 ] = next[ j ] + 1。

用反证法:假设存在  ()满足(7),则有,包含子情况:

,与 式(6) 矛盾。故原假设不成立,不存在这样的  ,得证!

2)  若,则必有,不符合next[ j + 1]的定义。

令  = next[  ] ,则成立,若,则进一步有,且是满足情况的最大k(证明与之前相仿),next[ j + 1 ] = next[  ] + 1 =  + 1。

同理,如果,令 = next[  ] ,若,则令 next[ j + 1 ] = next[  ] + 1=  + 1。若这代的k还不等于,就一路迭代直至第代为止--=next[],,next [ j + 1 ] = 

最底层的情况是 =next[] = 0,此时 “第二个串P” 移动,使其第1个位置和“第一个串P”的第 i + 1 个位置对齐,继续匹配。

以上递推的推导过程,需要一些想像力,一旦 get 到递推时,第二个串沿着第一个串蹭来蹭去的画面,就豁然开朗了。

求next函数的过程图例:

是不是觉得非常简单,非常清晰,非常明了!

使用C++代码来描述求next函数的过程:

//_next数组从1开始数起
//字符串从0开始数起void Next(string p) {int i = 1,j = 0 ; _next[1] = 0;while( i < p.length()){if(j == 0 || p[i-1] == p[j-1]){++ i;++ j;_next[i] = j;}elsej = _next[j];}
}

上面的KMP和Next两个函数的代码就构成了完整的KMP代码了,下面是我实现的代码,附带一个简例:

/******************************* author:      ace_yom (Peizhen Zhang)* date:        2015-8-17* description: KMP** copy right reserved.******************************/#include <iostream>
#include <string>
using namespace std;//_next数组从1开始数起
//字符串从0开始数起
const int maxn = 101;
int _next[maxn];//_next数组从1开始数起
//字符串从0开始数起
void Next(string p) {int i = 1,j = 0 ; _next[1] = 0;while( i < p.length()){if(j == 0 || p[i-1] == p[j-1]){++ i;++ j;_next[i] = j;}elsej = _next[j];}
}//这里的pos和函数的返回索引都是从1开始数起的
//字符串的索引是从0开始数起的
int KMP(string s , string p , int pos) {int i = pos, j = 1;int lenP = p.length(), lenS = s.length();while( i <= lenS && j <= lenP ){if( j == 0 || s[i-1] == p[j-1]){++ i;++ j;}elsej = _next[j];}if(j > lenP)return i - lenP;return -1;
}int main() {string s = "acbfyacafud";string p = "ac";Next(p);//将返回1cout << KMP(s,p,1);return 0;
}

设串S的长度为n,串P的长度为m,Next算法的时间复杂度为O(n+m)

以上,算法之优美简洁莫过于此~!

然而,你以为这样就结束了吗?No ~ ! next数组还可以进一步地优化。不过这就留给读者自己进行探究了吧。实在想不出来,可以参考 [1] 的末尾部分。


Reference:

[1] 数据结构(C 语言版) 严蔚敏 吴伟民 编著--4.3 串的模式匹配算法

字符串模式匹配--KMP之美相关推荐

  1. 算法笔记:简单的字符串模式匹配-KMP算法(与BF算法对比时间复杂度)

    简单的讲就是字符串不回溯. #include<stdio.h> #include<stdlib.h> #include<string.h>int countBF = ...

  2. 字符串模式匹配KMP算法详解(Python语言)

    问题描述 主串为 ′ababcabcacbab′ ′ a b a b c a b c a c b a b ′ 'ababcabcacbab',模式串为 ′abcac′ ′ a b c a c ′ 'a ...

  3. KMP字符串模式匹配详解

    KMP字符串模式匹配详解 KMP字符串模式匹配通俗点说就是一种在一个字符串中定位另一个串的高效算法.简单匹配算法的时间复杂度为O(m*n);KMP匹配算法.可以证明它的时间复杂度为O(m+n).. 一 ...

  4. KMP算法字符串模式匹配

    KMP字符串模式匹配详解 来自CSDN     A_B_C_ABC 网友 KMP字符串模式匹配通俗点说就是一种在一个字符串中定位另一个串的高效算法.简单匹配算法的时间复杂度为O(m*n);KMP匹配算 ...

  5. KMP字符串模式匹配

    KMP 字符串基本概念 字符串 S:无特殊说明,字符串仅由26个小写字母'a'-'z',并用大写字母表示一个字符串 S="abcd" |S|:表示一个字符串的长度 |S|=4 S[ ...

  6. KMP字符串模式匹配详解(zz)

    刚看到位兄弟也贴了份KMP算法说明,但本人觉得说的不是很详细,当初我在看这个算法的时候也看的头晕昏昏的,我贴的这份也是网上找的. 且听详细分解: KMP字符串模式匹配详解 来自CSDN     A_B ...

  7. 0x15.基本数据结构 — 字符串 (KMP算法(含详细证明)和最小表示法)

    目录 一.KMP模式匹配 1.引理: 2.引理证明: 3.使用优化的算法计算nextnextnext数组: 4.luogu P3375 [模板]KMP字符串匹配 5.UVA1328 Period 6. ...

  8. 数据结构之字符串模式匹配

    程序源代码:点击打开链接 1.引入 字符串模式匹配.首先我们引入目标串,模式串的概念,而字符串模式匹配就是查找模式串在目标串中的位置. 2.brute-Force算法 brute-Force算法,我的 ...

  9. 数据结构 串(字符串)与KMP

    文章目录 概述.定义 抽象数据类型(ADT, Abstract Data Type) 模式匹配 KMP算法 MP算法 KMP算法对MP算法的改进 C 实现 概述.定义 串(字符串,String)是由零 ...

最新文章

  1. matlab 仿真步长,MATLAB Simulink变步长仿真与固定步长仿真简单对比
  2. 当下火热的大数据视频,免费送(含源码)
  3. jackson 反序列化string_java – 使用Jackson对数组进行反序列化
  4. mybatis一对多关联 创建_MyBatis多对多关联查询(级联查询)
  5. python程序实例教程基础-Python程序设计实例教程
  6. Swift调用第三方OC项目
  7. 【地理建模】现代地理学中的数学方法:主成分分析法案例详解
  8. 波士顿动力放出新视频:谁都挡不住机器狗开你的门
  9. python中number函数_Python 数字(Number)
  10. 复练-面试的科技树-我是谁、我喜欢、我能够
  11. ie提示保护计算机关闭网页,xp系统解决IE为保护计算机关闭网页方法分享
  12. fragment怎么获得上下文环境_Flask 源码剖析 (三):Flask 的上下文机制 (上)
  13. Viewport 不权威指南
  14. (转 )Unity对Lua的编辑器拓展
  15. 十次方项目登陆问题 token令牌解析,claims获取不到userid,求大神帮忙看下代码解决问题
  16. 【408考研笔记】操作系统完整知识点
  17. Centos 下安装 文泉驿 字体 Odoo
  18. pycharm 常用快捷键(中英文对照表)
  19. 动词ing形式的5种用法_动词-ing形式用法归纳
  20. 新百家姓出来了,看你排第几位?

热门文章

  1. 浙江数字贸易交易会筹备期间,马云联合国分享数字经济浙江经验
  2. PHP Object 对象
  3. 安全测试 : 小米(xiaomi.com)网站短信接口安全测试,如何保护短信接口?
  4. 弹出窗口,转向下一页
  5. powerbi使用说明_微软Power BI入门指南(1):Power BI初步介绍
  6. 《游戏设计艺术(第二版)》第八章个人学习
  7. 摄像头V4L2获取的YUY2格式转YUV420格式
  8. 基于JAVA社区生活超市管理系统计算机毕业设计源码+系统+mysql数据库+lw文档+部署
  9. 品茗安全帮助html,品茗安全计算软件操作..docx
  10. 【自习任我行】任务跟踪1