初识KMP
KMP简介
KMP是一种由Knuth(D.E.Knuth)、Morris(J.H.Morris)和Pratt(V.R.Pratt)设计的字符串匹配算法。对目标串T[0:n-1]中查找与之匹配的模式串P[0:m-1],KMP算法的时间复杂度为O(n+m),它是核心思想是通过目标串和模式串已经部分匹配的信息,减少或者避免冗余的比较,达到提高效率的目的。
暴力匹配简介
通常情况下,C/C++初学者是采用"暴力匹配"算法来解决目标串T[0:n-1]和模式串P[0:m-1]的匹配问题。"暴力匹配"的算法用自然语言描述,如下所示。
- 若strlen(T)<strlen(P), 则匹配失败,否则进行第二步。其中strlen(P)表示字符串P的长度。
- 用P的第j位(初始j=0)与T的第i位(初始i=0)进行比较,若P[j] == T[i],则分别比较两者的下一位;若 P[j] != T[i],则用P的首位与T的下一位进行比较。
- 重复第2步,直到模式串已经全部匹配成功或者匹配失败。全部匹配成功时,j>=strlen(P);匹配失败时,i>=strlen(T)且j<strlen(P)。
"暴力匹配"算法的代码如图4-1所示。
int StringMatch(char * T, char * P)
{ //匹配失败返回-1,匹配成功返回T[0:n]中匹配字符串首字符位置
int i = , j = , len1 = strlen(T), len2 = strlen(P);
if(len1 < len2)
return -;
while( i<len1 && j < len2){
if(P[j] == T[i])
i++, j++;
else
i++;
}
if(j>=len2)
return i - len2;
else
return -;
}
图4-1 "暴力匹配"的C语言代码
冗余的原因
KMP算法是对暴力匹配进行改进,减少于冗余比较次数。那么,暴力匹配的"冗余比较"出现在哪里呢?
表4-2 字符串匹配
| 下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| 目标串T | A | B | C | B | B | A | B | C | D |
| 模式串P | A | B | C | D |
如表4-2所示,T[0:2]和P[0:2]已经匹配,只有T[3] != P[3],按照暴力匹配的算法,把P[0]与T[1]进行比较。注意目标串的T[0:3]已经比较过了,已经确定T[0:3]的字符的值。暴力匹配需要把指向T[3]的指针回溯到T[1],重新进行比较。这就造成冗余!按照人的思维进行字符串匹配来思考,这时知道T[1:3]中没有出现字符'A',所以不可能匹配,因此需要越过T[1:3],进行T[4]与P[0]比较。
特征向量next
机器只会执行,即使比较过了,它也不会记住T[1:3]中没有'A'。因此,需要一个特征向量next来让标记已经匹配的子串信息。next是一个数组,与模式串密切相关,长度与模式串相等,定义如下。

图4-3 next数组的定义
由图4-3中可以得出,next[0] = -1,当存在正整数k(0<k<j),使得P[0...k-1] = P[j-k...j-1]成立的最大k值即为next[j]的值,不存在这样的k时,next[j]=0。其中P[0...k-1] = P[j-k...j-1]是指,模式串最前面的k位字符与刚好当前位置j之前倒数k个字符相匹配。如表4-4所示,构造出模式串的next特征向量。
| 下标j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| 模式串P | A | B | A | B | B | B | A | B | A |
| next[j] | -1 | 0 | 0 | 1 | 2 | 0 | 0 | 1 | 2 |
- j = 0, next[j] = -1
- j = 1, 因为0<k<j, 因此不存在正整数k, next[1] = 0
- j = 2, 当k=1,P[0] != P[1], next[2] = 0
- j = 3, 存在最大值k=1,满足P[2] = P[0], next[3] = 1
- j = 4, 存在最大值k=2, 满足P[0,1] = P[2,3], next[4] = 2
- j = 5, 不存在最大值k=3,满足P[0,1,2] = P[2,3,4], P[4] != P[0], next[5] = 0
- j = 6, P[5] != P[0], next[6] = 0
- j = 7, 存在最大值k=1, P[6] = P[0], next[7] = 1
- j = 8, 存在最大值k=2, P[6,7] = P[0,1], next[8] = 2
由此,可以设计出求解next的算法,相应的C语言代码如图4-5所示。
void setNext(int * next)
{ //求特征向量next的数组元素值, P为模式串, T为目标串
int j=0, k=-1,len = strlen(P);
while(j<len-1){
if(k == -1 || P[k]== T[j])
next[++j]= ++k;
else
k = next[j];
}
}
图4-5 求解next的C程序
next的使用
next数组元素保存已经部分匹配的字符串信息,根据next数组元素值,可以跳过已经比较过的冗余的比较步骤。
| 下标i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 目标串T | A | B | A | B | A | B | A | B | B | B | A | B | A |
| 模式串P | A | B | A | B | B | B | |||||||
| A | B | A | B | B | B | ||||||||
| A | B | A | B | B | B | ||||||||
| next[j] | -1 | 0 | 0 | 1 | 2 | 0 |
如表4-6所示,当i=4, j=4时,T[i] != P[j], 需要移动模式串P与目标串相应比较的位置对齐。需要移动的多少从next数组的元素值可以得到。当j = 4, next[j] = 2,根据next的定义,P[0,1] = P[2,3],又T[0:3] = P[0:3],所以P[0,1] = T[2,3],因此只需比较T[4] 和 P[2]即可。此时i = 4, j = 2,j的值由4回溯为2,是根据j = next[j]得到的。j经过回溯后,P[0:j-1] = T[i-j:i-1]已经是匹配的。同理,当i = 6, j = 5, T[i] != P[j], 查next数组next[5] = 2, j回溯 j=4=>j=2。
所以next数组的作用是:
当P[j] != T[i]时,j的值由j = next[j],快速回溯到需要比较的位置, 并且P[0,j-1]必定与T[i-j, i-1]匹配。
思考: next数组的求解方法有没有需要优化的地方?
| 下标i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 目标串T | A | A | B | C | B | A | B | C | A | A | B | C | A | B | A |
| 模式串P | A | B | C | A | A | B | A | B | C | ||||||
| A | B | C | A | A | B | A | B | C | |||||||
| A | B | C | A | A | B | A | B | C | |||||||
| next[j] | -1 | 0 | 0 | 0 | 1 | 1 | 2 | 1 | 2 |
如表4-7所示,已知 T[1:3] = P[0:2], T[4] != P[3]。 此时 k = next[j] = 0, P[k] = P[j] = 'A'。又已知P[j] != T[i],根据之前的next数组求解,此时仍然进行P[k] 与 T[i]的比较。很明显,此时P[k] != T[i],
所以此次比较属于冗余!P[k] = P[j] 是在求解next数组的时候已知的,而特征向量,即next数组并没有显示出这个信息。所以,需要对next数组的求解进行优化,进一步减少冗余比较次数。
void setNext(int * next)
{ //求特征向量next的数组元素值, P为模式串, T为目标串
int j=, k=-,len = strlen(P);
while(j<len-){
if(k == - || P[k]== T[j])
{
j++, k++;
if(P[j] = P[k]) //对next数组求解进行优化
next[j] = next[k];
else
next[j] = k;
}
else
k = next[j];
}
}
KMP算法的C代码
int KmpMatch(char T[], char P[], int * next, int start)
{//在目标串T[]中的start位置开始匹配模式串P,next为P的特征向量数组首地址,若匹配成功则返回模式串首字符在T中r位置,否则返回-1
int i = start, j = 0, plen = strlen(P), tlen = strlen(T);
if( plen > tlen )
return -1;
while( i < tlen && j< plen){
if( j == -1 || T[i] == P[j])
i++, j++;
else
j = next[j];
}
if( j >= plen )
return i - plen;
else
return -1;
}
扩展KMP算法(待续)
初识KMP的更多相关文章
- HDU_1711_初识KMP算法
Number Sequence Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) ...
- 【初识】KMP算法入门(转)
感觉写的很好,尤其是底下的公式,易懂,链接:http://www.cnblogs.com/mypride/p/4950245.html 举个例子 模式串S:a s d a s d a s d f a ...
- 【初识】KMP算法入门
举个例子 模式串S:a s d a s d a s d f a s d 匹配串T:a s d a s d f 如果使用朴素匹配算法—— 1 2 3 4 5 6 8 9 a s d a s d a s ...
- KMP算法求解
// KMP.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include<iostream> using namespac ...
- 简单有效的kmp算法
以前看过kmp算法,当时接触后总感觉好深奥啊,抱着数据结构的数啃了一中午,最终才大致看懂,后来提起kmp也只剩下“奥,它是做模式匹配的”这点干货.最近有空,翻出来算法导论看看,原来就是这么简单(先不说 ...
- Android动画效果之初识Property Animation(属性动画)
前言: 前面两篇介绍了Android的Tween Animation(补间动画) Android动画效果之Tween Animation(补间动画).Frame Animation(逐帧动画)Andr ...
- 初识Hadoop
第一部分: 初识Hadoop 一. 谁说大象不能跳舞 业务数据越来越多,用关系型数据库来存储和处理数据越来越感觉吃力,一个查询或者一个导出,要执行很长 ...
- python学习笔记(基础四:模块初识、pyc和PyCodeObject是什么)
一.模块初识(一) 模块,也叫库.库有标准库第三方库. 注意事项:文件名不能和导入的模块名相同 1. sys模块 import sys print(sys.path) #打印环境变量 print(sy ...
- KMP算法
KMP算法是字符串模式匹配当中最经典的算法,原来大二学数据结构的有讲,但是当时只是记住了原理,但不知道代码实现,今天终于是完成了KMP的代码实现.原理KMP的原理其实很简单,给定一个字符串和一个模式串 ...
随机推荐
- linux中的fork函数的基本用法
代码: #include <iostream> #include <string> #include <cstdio> #include <unistd.h& ...
- Windows环境变量修改
做开发的时候都设置过Windows的环境变量,一直觉得实在是难用之极.网上搜索过解决方案,有个开源的小程序,试过,有问题,没有办法正常运行.所以自己写一个,权当练手. 下载地址:http://file ...
- 浅谈Hive vs HBase
Hive是什么? Apache Hive是一个构建于Hadoop(分布式系统基础架构)顶层的数据仓库,注意这里不是数据库.Hive可以看作是用户编程接口,它本身不存储和计算数据:它依赖于HDFS(H ...
- php的一些小笔记-文件函数(1)
---恢复内容开始--- 与文件操作相关的函数有一部分可以和linux命令比较,但是我觉得可能还是linux上使用的比较频繁 如:chown,chmod,chgrp,rename,touch,link ...
- 根据identifier从StoryBoard中获取对象,UIButton的图片文件位置
@import url(http://i.cnblogs.com/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/c ...
- List myList=new ArrayList()的理解
ArrayList不是继承List接口,是实现了List接口.你写成ArrayList arrayList = new ArrayList();这样不会有任何问题.和List list = new A ...
- Codeforces 463D Gargari and Permutations
http://codeforces.com/problemset/problem/463/D 题意:给出k个排列,问这k个排列的最长公共子序列的长度. 思路:只考虑其中一个的dp:f[i]=max(f ...
- 暴力破解UltraEdit v21 无需注册
一.复制一份UltraEdit安装目录中的主程序uedit32.exe,到任意目录,用UltraEdit打开复制的uedit32.exe文件. 二.修改以下内容 原来:00094750h: BE DC ...
- windows可以登录qq,但无法打开浏览器页面
1.检查dns是否配置. 2.检查hosts是否有异常. 3.电脑上装有抓包软件,为IE设置代理服务器了. 4.360网络修复.
- Hive 2、Hive 的安装配置(本地MySql模式)
一.前提条件 安装了Zookeeper.Hadoop HDFS HA 安装方法: http://www.cnblogs.com/raphael5200/p/5154325.html 二.安装Mysq ...