1. 朴素算法的改进

(1)朴素算法的优化线索

  ①因为 Pa != Pb 且Pb==Sb;所以Pa != Sb;因此在Sd处失配时,子串P右移1位比较没有意义,因为前面的比较己经知道了Pa != Sb,可以利用己经比较过的事实,而不必进行第2轮的比较,从而提高效率。

  ②KMP算法就是为解决这一问题而提出的!

(2)部分匹配与前后缀(以S字符串“ABCDAB”为例)

  ①前缀除了最后一个字符以外,一个字符的全部头部组合的集合。如,字符串S的前缀有{A,AB,ABC,ABCD,ABCDA},其中ABCDA为最大前缀。

  ②后缀除第一个字符以外,一字符串的全部尾部组合的集合。字符串“ABCDAB”的后缀有{B,AB,DAB,CDAB,BCDAB},其中BCDAB为最大后缀。

  ③部分匹配值:最长相同前缀和后缀的长度。如S串的最大相同前缀和后缀为“AB”。以下是“abxabxabc”字符串的部分匹配值示例:

模式串

a

b

x

a

b

x

a

b

c

部分匹配值

0

0

0

1

2

3

4

5

0

2. kmp字符匹配原理

(1)kmp算法中主要指针的移动规律

  ①kmp根据模式串本身携带的内部信息,在匹配失败时主串指针不回退,而是最大的移动模式串以减少匹配次数,而这依赖于部分匹配值表。

  ②失配时j指针的移动规律:在己经匹配的模式子串中找出最大的相同前缀和后缀(A),然后移动并使它们重叠(如上图如示)。这一过程相当于将模式串j指针从当前位置b (后缀的下一个字符)左移到前缀的下一个字符的位置

  ③而j指针要左移到的目标位置到底是在哪里,其数值被记录在next[j]中!(注意:next[j]表示当前部分匹配中最大相同前后缀的长度,也是匹配失败时j指针要移动到的目标位置

(2)为什么匹配失败时,模式串指针可以从后缀一次性左移到前缀的下一个字符处?

  ①讨论1:假设下图蓝色部分不包含“ab”字符串

  ②讨论2:假设下图蓝色部分包含“ab”字符串

3. next数组

3.1 图解next数组:假设主串S,模式串P。

(1)假设当前已填写完i个元素的next值(即next[0]、next[1]…next[i-1],注意元素个数与数组索引的不同!)。假设next[i-1]==k,其含义为在i左侧找到了一个最大相同前后缀(前缀为A1,后缀为A2,可得A1==A2),其长度为k。

(2)同理,填完k个元素的next值后,从next[k-1]可得k左侧的最大前后缀为B1和B2,所以B1=B2=B3。填完next[k]个元素之后,可得其前后缀C1==C2(=C3=C4)。

(3)问题转换为当在P[i]失配时,如何填写next[i]的值?从上面的分析可以看出,P[i]左侧的前后缀按长度从大到小依次为A2、B3、C4,这些前后缀可以拿来做文章。也就是P[i]的最大前后缀只可能在A2、B3、C4等基础上增加,实际上会按“A2→B3→C4→…”的顺序开始判断(贪心法,从最长串开始,不行则求其次)。

3.2 求解next数组过程

(1)动态规划(类似于数学归纳法)

  ①初始状态:k=0,j=0,next[0]=0。

  ②假设已经填完i个元素的next值(即next[0]、next[1]…next[i-1])

  ③现在递推,当有i+1个元素时如何填写next[i]的值

(2)求解  

  ①如果此时P[i]==P[k]前缀为A1+P[k]后缀为A2+P[i],显然两者相同。因此,next[i]填入 k + 1;(即在A2长度的基础上加1)

  ②如果P[i] != P[k],表示此时的最大前后缀已经不可能是A2+P[i]了。按照贪心法,接下来会判断前后缀有没有可能是B3+P[i],这需要对比②线两端的元素是否相同,先让k = next[k-1](其中next[k-1]表示B1(或B3)串的长度),再判断此时的P[k](=P[next[k-1]])是否等于P[i]。如果相等,则最大前后缀就是B3+P[i],next[i]=B3的长度+1,即此时的k+1。如果仍不相等,则判断前后缀有没有可能C4+P[i],这时需对比③线再端的元素。如果相等,则next[i]=C4的长度加1,即当前的k+1。如果不相等,会去找更小的前后缀,然后一直对比下去,直到k==0时,表示没找到,next[i]的长度为0

【编程实验】

//main.cpp

#include <iostream>
#include <string.h>
using namespace std; //部分匹配表(生成next数组)
void makeNext(const char* p, int next[])
{
int len = strlen(p); //初始化状态
int i = ;
int k = ;
next[] = ; for (i = ; i<len; i++) //从第2个字符开始
{
//找到p[i]之前可能的最大相同前后缀长度
while ((k > ) && (p[i] != p[k]))
k = next[k - ]; //找到p[i]之前可能的最大前后缀以后,判断是否可以
//在之个最大的前后缀加上p[i]这个字符
if (p[i] == p[k]) {
++k;
} next[i] = k;
}
} //kmp算法
int kmp(const char* t, const char* p)
{
int tLen = strlen(t);
int pLen = strlen(p); int ret = -; if ( (t != NULL) && (p != NULL) && (tLen >= pLen) )
{
//创建next数组
int* next = new int[pLen];
makeNext(p, next); int j = ; for (int i= ; (j < pLen) && (i < tLen); i++)
{
while (( j > ) && (t[i] != p[j]))
{
j = next[j]; //失配时,移动j到前缀后面。如果仍然失配,j一直往模式串
//开始处的方向移动,直到匹配或j到达了模式串开始的位置。
} if (t[i] == p[j]) //匹配时,继续查找一下
j++; if (j == pLen)
ret = i - pLen + ;
} delete next;
} return ret;
} int main(void)
{
char t[] = "xyzababxabxab";
//char t[] = "ababxabxabab";
char p[] = "abxabxab"; cout << kmp(t, p) << endl; return ;
}

4. 小结

(1)部分匹配表是提高子串查找效率的关键

(2)部分匹配表定义为最大相同前缀和后缀的长度,也是失配时模式串指针要移动到的目标位置。

(3)可以用递推的方法产生部分匹配表

(4)KMP利用部分匹配值与子串指针移动的关系提高查找效率。

第41课 kmp子串查找算法的更多相关文章

  1. 第四十一课 KMP子串查找算法

    问题: 右移的位数和目标串没有多大的关系,和子串有关系. 已匹配的字符数现在已经有了,部分匹配值还没有. 前六位匹配成功就去查找PMT中的第六位. 现在的任务就是求得部分匹配表. 问题:怎么得到部分匹 ...

  2. 数据结构开发(14):KMP 子串查找算法

    0.目录 1.KMP 子串查找算法 2.KMP 算法的应用 3.小结 1.KMP 子串查找算法 问题: 如何在目标字符串S中,查找是否存在子串P? 朴素解法: 朴素解法的一个优化线索: 示例: 伟大的 ...

  3. 字符串类——KMP子串查找算法

    1, 如何在目标字符串 s 中,查找是否存在子串 p(本文代码已集成到字符串类——字符串类的创建(上)中,这里讲述KMP实现原理) ? 1,朴素算法: 2,朴素解法的问题: 1,问题:有时候右移一位是 ...

  4. KMP字符串查找算法

    #include <iostream> #include <windows.h> using namespace std; void get_next(char *str,in ...

  5. 串、串的模式匹配算法(子串查找)BF算法、KMP算法

    串的定长顺序存储#define MAXSTRLEN 255,//超出这个长度则超出部分被舍去,称为截断 串的模式匹配: 串的定义:0个或多个字符组成的有限序列S = 'a1a2a3…….an ' n ...

  6. KMP 算法 & 字符串查找算法

    KMP算法 Knuth–Morris–Pratt algorithm 克努斯-莫里斯-普拉特 算法 algorithm kmp_search: input: an array of character ...

  7. LOJ #103. 子串查找 (Hash)

    题意 给定两个字符串 \(A\) 和 \(B\),求 \(B\) 在 \(A\) 中的出现次数. 思路 这是一道 \(KMP\) 的模板题. 不过 \(Hash\) 是个好东西,可以用 \(Hash\ ...

  8. 七大查找算法(附C语言代码实现)

    来自:Poll的笔记 - 博客园 链接:http://www.cnblogs.com/maybe2030/p/4715035.html 阅读目录 1.顺序查找 2.二分查找 3.插值查找 4.斐波那契 ...

  9. 深入JDK源码之Arrays类中的排序查找算法(转)

    原文出处: 陶邦仁 binarySearch()方法 二分法查找算法,算法思想:当数据量很大适宜采用该方法.采用二分法查找时,数据需是排好序的. 基本思想:假设数据是按升序排序的,对于给定值x,从序列 ...

随机推荐

  1. Gravitational Teleport docker-compose组件独立部署运行

    Gravitational Teleport 可以作为堡垒机进行使用,上次写过一个all in one 的,这次参考官方 的配置运行一个proxy node auth 分离的应用 备注: 基于dock ...

  2. 一个License的所带来问题

    在维护一个老产品时发现一个License的问题.产品是用Z80 Z8F6423, compiler用的是ZDS II Z8 Encode! 4.9.0. 由于有一个Bug要修复,所以我重新检查了一下它 ...

  3. C# WinForm 菜单项的大小、高宽的手动控制

    控制菜单项的第一级的下级菜单项的大小: 直接通过(ContextMenuStrip对象).AutoSize = false.(ContextMenuStrip对象).Size = new Size(5 ...

  4. str_replace中的匹配空白符,必须用双引号

    例: $minUnit = str_replace(array('\r','\n'),"",$content); 执行上面的语句,你会发现,文本没有任何变化,该换行的地方还是换行. ...

  5. 使用 localeCompare 函数对中文进行排序

    localeCompare 函数可对中文进行排序. 性能相关 当比较大量字符串时, 比如比较大量数组时, 最好创建一个Intl.Collator 对象并使用compare 属性所提供的函数. mdn介 ...

  6. Scala实战

    1. Eclipse中设置scala调试器 scala的调试器要选择Scala Application(new debuger)Launcher:开始选择图省事,选择了一个字母少的,亲切的Launch ...

  7. 学习大数据基础框架hadoop需要什么基础

    什么是大数据?进入本世纪以来,尤其是2010年之后,随着互联网特别是移动互联网的发展,数据的增长呈爆炸趋势,已经很难估计全世界的电子设备中存储的数据到底有多少,描述数据系统的数据量的计量单位从MB(1 ...

  8. Ribbon 常用配置

    配置参数 默认值 说明 <client>.<namespace>.listOfServers 配置服务器列表 <client>.<namespace>. ...

  9. shopnc-setNcCookie-后台验证码

    function setNcCookie($name, $value, $expire='3600', $path='', $domain='.a.cn', $secure=false){ if (e ...

  10. SDRAM读写操作

    SDRAM读写操作 1.读操作 2.写操作 SDRAM所有时序参数都可以在芯片手册上查到.