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. BizDevOps — the true value proposition of workflow engines

    转自:https://blog.bernd-ruecker.com/bizdevops-the-true-value-proposition-of-workflow-engines-f342509ba ...

  2. 原生js实现ajax用于简单的签到或登录

    <script> function createStandardXHR() {     try {         return new window.XMLHttpRequest(); ...

  3. C# to IL 12 Arrays(数组)

    An array is a contiguous block of memory that stores values of the same type. These valuesare an ind ...

  4. IBM MQ相关 ---- 系列文章

    原 IBM websphere MQ远程队列的简单配置 转 MQ7.5以后的权限问题解决 原 MQ--API总结 转 连接IBM MQ原因码报2035的错误解决办法 原 Java连接MQ的实例 转 通 ...

  5. sqoop上传数据到hdfs,并用hive管理数据。

    sqoop导入mysql数据表到HDFS中sqoop import --connect jdbc:mysql://master:3306/test --username root --password ...

  6. Day 01 计算机组成和操作系统

    一.编程与编程语言 1.什么是语言?什么是编程语言? 答:语言是一种事物与另一种事物沟通的介质. 编程语言是程序员与计算机沟通的介质. 2.什么是编程? 答:程序员把自己想要让计算机做的事用编程语言表 ...

  7. jdk源码剖析二: 对象内存布局、synchronized终极原理

    很多人一提到锁,自然第一个想到了synchronized,但一直不懂源码实现,现特地追踪到C++层来剥开synchronized的面纱. 网上的很多描述大都不全,让人看了不够爽,看完本章,你将彻底了解 ...

  8. 【转】 Ubuntu在启动器添加程序快捷方式

     转自: http://blog.csdn.net/walker0411/article/details/51555821 目录(?)[-] Ubuntu在启动器添加程序 eclipse快捷方式的创建 ...

  9. HD-SDI制式学习

    HD-SDI制式学习 1.资料来源(XAPP514) 2.HD-SDI相关知识 需要注意的是每一行的开始为EAV,序列依次为:EAV Blank Video 3.XYZ的格式 4.Line numbe ...

  10. mysql编译安装(详细)(转载)

    mysql编译安装(详细)   一.编译安装MySQL前的准备工作 安装编译源码所需的工具和库 yum install gcc gcc-c++ ncurses-devel perl 安装cmake,从 ...