KMP及其改进算法
本文主要讲述KMP已经KMP的一种改进方法。若发现不正确的地方,欢迎交流指出,谢谢!
KMP算法的基本思想:
KMP的算法流程:
每当一趟匹配过程中出现字符比较不等时,不需回溯 i 指针,而是利用已经得到的部分匹配的结果将模式向右滑动尽可能远的一段距离后,继续进行比较。
设S为目标串,T为模式串,设 i 指针和 j 指针分别指示目标串和模式串中正待比较的字符。
开始时,令i=0,j=0。如果Si==Tj,则使i和j的值分别增加l;反之,i不变,j的值退回到j=next[j]的位置(即模式串右滑),然后再对Si和Tj进行比较。依次类推,直到出现下列两种情况之一:
1.j值退回到某个j=next[j]时,有Si==Tj,则指针的值各增加1后,再继续匹配;
2.j值退回到 j=-1,此时令指针的值各增加1,也即下一次对Si+1和T0进行比较。
模式匹配KMP算法的时间复杂度为O(m+n),只有当模式与珠串之间存在许多“部分匹配”的情况下显得比朴素字符匹配算法快得多。但是KMP算法最大的特点就是指示主串的指针不需要回溯,整个过程中,对主串仅需从头至尾扫描一遍,这对处理从外设输入的庞大文件很有效,可以边读入边匹配,而无需回头重读。
跟朴素匹配算法的主要差异:
在于当Si != Tj的时候,朴素算法采用的是将Tj往前推一格,然后将j置为0,重新进行匹配,而KMP采用的方法是将j置为next[j],然后再匹配。
很显然,这里的next[j]是算法的核心。
下面是next[j]的计算方法,以及代码的实现:
- void get_nextval( const char *s, int *nextval)
- {
- int len = strlen(s);
- int i = 0, j = -1;
- nextval[0] = -1;
- while( i < len-1 ){
- if( j == -1 || s[i] == s[j] ){
- ++i;
- ++j;
- if( s[i] != s[j] ){
- nextval[i] = j;
- }else{
- nextval[i] = nextval[j];
- }
- }else{
- j = nextval[j];
- }
- }
- return ;
- }
得到了next[j]之后,KMP算法的实现就很简单了,按照上面KMP的算法流程,可以很快写出代码:
- //s为匹配串
- //t为主串
- int kmp( const char *s, const char *t )
- {
- int k = -1;
- int nextval[N] = {0};
- int s_len = strlen(s);
- int t_len = strlen(t);
- get_nextval( s, nextval ); //get_nextval[]
- cout<<"nextval:"<<endl;
- for( k = 0; k < s_len; k++)
- cout<<nextval[k]<<" ";
- cout<<endl;
- int i = 0, j = 0;
- while( i < t_len && j < s_len ){
- if( j == -1 || t[i] == s[j] ){
- i++;
- j++;
- }else{
- j = nextval[j];
- }
- }
- if( j >= s_len ){
- return i-s_len;
- }else{
- return -1;
- }
- }
下面给出一个KMP的实现及测试代码:
- #include <iostream>
- using namespace std;
- #define N 100
- void get_nextval( const char *s, int *nextval);
- int kmp( const char *s, const char *t );
- //s为匹配串
- //t为主串
- int kmp( const char *s, const char *t )
- {
- int k = -1;
- int nextval[N] = {0};
- int s_len = strlen(s);
- int t_len = strlen(t);
- get_nextval( s, nextval ); //get_nextval[]
- cout<<"nextval:"<<endl;
- for( k = 0; k < s_len; k++)
- cout<<nextval[k]<<" ";
- cout<<endl;
- int i = 0, j = 0;
- while( i < t_len && j < s_len ){
- if( j == -1 || t[i] == s[j] ){
- i++;
- j++;
- }else{
- j = nextval[j];
- }
- }
- if( j >= s_len ){
- return i-s_len;
- }else{
- return -1;
- }
- }
- void get_nextval( const char *s, int *nextval)
- {
- int len = strlen(s);
- int i = 0, j = -1;
- nextval[0] = -1;
- while( i < len-1 ){
- if( j == -1 || s[i] == s[j] ){
- ++i;
- ++j;
- if( s[i] != s[j] ){
- nextval[i] = j;
- }else{
- nextval[i] = nextval[j];
- }
- }else{
- j = nextval[j];
- }
- }
- return ;
- }
- int main()
- {
- char s[N], t[N];
- while( cin>>s >>t ){
- int i = 0;
- i = kmp( s, t );
- cout <<"ans = " <<i <<endl;
- }
- return 0;
- }
测试如下:

KMP模式匹配问题的改进思想和方法
KMP的不足之处:
通过观察,我们可以在原有的KMP算法中,发现一个不足的地方,也就是我们将要改进的地方就是,因为,子串的出现是随机的,如果子串在主串出现的位置靠后的时候,KMP算法实在显得比较低效。现在我们给出一个例子来说明问题。
主串为:aspowqeursoolksnkhiozbgwoinpweuirabaac
子串为:abaac
容易看出,子串要到最后才会得到匹配,因此,我们提出我们的思想——从主串的首和尾同时进行匹配,那样,就可以提高算法的效率,并且,除了在这个方面提高算法效率以外,我们还想到,当 m >>n,,n>>0的时候(m为主串长度,n为子串长度),并且子串并没有在主串中出现的话,那么,在改进算法中,我们将不需要比较到最末才判断是否存在匹配的子串,而是通过剩下的字符数,来判断是否存在足够的字符与子串匹配,如果不足的话,那样就不存在,否则就继续匹配下去。
如何实现从主串末尾想串头开始匹配呢?
我们这里有两个方案:第一个方案是,把子串逆转,然后沿用旧的KMP算法中的next函数求出其逆转后的子串的next值,再用以进行匹配;第二个方案就是,不需要把子串逆转,而是采用一个新的next函数直接求出其逆转后的next值。
第一二个方案比较后,我们选择第二个方案。因为,在 n>>0的时候,明显地,在把子串逆转的时候同时需要多一个字符串来存放,并且,在不同的匹配都需要一个新的字符串,这样就大大地浪费空间了,除此之外,第一个方案至少要做遍历子串两次,而第二个方案只需要遍历子串一次就可以了。所以我们决定采用构建一个新的next函数来求出其逆转后的子串next值。
我们新的next函数的思想就是,把末字符看成是首字符,然后,仿照KMP算法中的next函数的实现方式最终实现的。现在,我们给出实现的新的next函数:
- void nextres( char* p, int *next, int n )
- {
- int i, j, k;
- i = n-1, j = -1;
- *next = -1;
- k = n;
- while( i > 0 ){
- if( j == -1 || *(p+i) == *(p+k-j-1)){
- i--, j++;
- if( *(p+i) != *(p+k-j-1) )
- *(next+n-i-1) = j;
- else
- *(next+n-i-1) = *(next+j);
- }
- else
- j = *(next+j);
- }
- }
在得到逆转后的子串的next函数后,我们就可以进行串的匹配了。其基本思路同原KMP算法,下面我们就给出匹配过程的实现:
- int march( char* mainhead, char* head, int mainlen, int lenth, int *next1, int *next2 )
- {
- int i, j, k, l, m;
- i = 0, j = 0, k = mainlen-1, m = lenth-1, l = 0;
- while( (m>0 && j<lenth) || lenth == 1 ){
- if( lenth == 1 && ( *(mainhead+i) == *(head+j) ||
- *(mainhead+k) == *(head+m)))
- return 1;
- if( j == -1 || *(mainhead+i) == *(head+j))
- i++, j++;
- else
- j = *(next1+j);
- if( l == -1 || *(mainhead+k) == *(head+m)){
- k--;
- l++;
- m = lenth==2?m-l:m-1;
- }else{
- l = *(next2+1);
- if( l != -1 )
- m = m-l;
- else
- m = lenth-1;
- }
- if( k-i < m-j )
- return 0;
- }
- if( m <= 0 || j >= lenth)
- return 1;
- else
- return 0;
- }
新的KMP算法在某种程度上的确可以提高模式匹配的效率。除此以外,新的模式匹配算法还能提早结束不必要的匹配。
from: http://blog.csdn.NET/cyh_24/article/details/8162436
- 顶
- 0
- 踩
KMP及其改进算法的更多相关文章
- 【Java】 大话数据结构(8) 串的模式匹配算法(朴素、KMP、改进算法)
本文根据<大话数据结构>一书,实现了Java版的串的朴素模式匹配算法.KMP模式匹配算法.KMP模式匹配算法的改进算法. 1.朴素的模式匹配算法 为主串和子串分别定义指针i,j. (1)当 ...
- 大话数据结构(8) 串的模式匹配算法(朴素、KMP、改进算法)
--喜欢记得关注我哟[shoshana]-- 目录 1.朴素的模式匹配算法2.KMP模式匹配算法 2.1 KMP模式匹配算法的主体思路 2.2 next[]的定义与求解 2.3 KMP完整代码 2.4 ...
- 排序系列 之 简单选择排序及其改进算法 —— Java实现
简单选择排序算法: 基本思想: 在待排序数据中,选出最小的一个数与第一个位置的数交换:然后在剩下的数中选出最小的数与第二个数交换:依次类推,直至循环到只剩下两个数进行比较为止. 实例: 0.初始状态 ...
- 第四十一课 KMP子串查找算法
问题: 右移的位数和目标串没有多大的关系,和子串有关系. 已匹配的字符数现在已经有了,部分匹配值还没有. 前六位匹配成功就去查找PMT中的第六位. 现在的任务就是求得部分匹配表. 问题:怎么得到部分匹 ...
- 数据结构开发(14):KMP 子串查找算法
0.目录 1.KMP 子串查找算法 2.KMP 算法的应用 3.小结 1.KMP 子串查找算法 问题: 如何在目标字符串S中,查找是否存在子串P? 朴素解法: 朴素解法的一个优化线索: 示例: 伟大的 ...
- 读论文《BP改进算法在哮喘症状-证型分类预测中的应用》
总结: 一.研究内容 本文研究了CAL-BP(基于隐层的竞争学习与学习率的自适应的改进BP算法)在症状证型分类预测中的应用. 二.算法思想 1.隐层计算完各节点的误差后,对有最大误差的节点的权值进行正 ...
- POJ 3155 Hard Life(最大密度子图+改进算法)
Hard Life Time Limit: 8000MS Memory Limit: 65536K Total Submissions: 9012 Accepted: 2614 Case Ti ...
- 字符串类——KMP子串查找算法
1, 如何在目标字符串 s 中,查找是否存在子串 p(本文代码已集成到字符串类——字符串类的创建(上)中,这里讲述KMP实现原理) ? 1,朴素算法: 2,朴素解法的问题: 1,问题:有时候右移一位是 ...
- 字符串模式匹配算法系列(三):Trie树及AC改进算法
Trie树的python实现(leetcode 208) #!/usr/bin/env python #-*- coding: utf-8 -*- import sys import pdb relo ...
随机推荐
- HashMap 的底层原理
1. HashMap的数据结构 数据结构中有数组和链表来实现对数据的存储,但这两者基本上是两个极端. 数组 数组存储区间是连续的,占用内存严重,故空间复杂的很大.但数组的二分查找时间复杂度小,为O(1 ...
- 快速获取表单多条数据,使用ajax传递给后台
当表单中有多条数据需要向后台传递时,一个一个的获取显然是不可取的办法,可以借助表单的serialize()方法获取. HTML: <form id="form"> &l ...
- kubernetes入门(08)kubernetes单机版的安装和使用
kubectl get - 类似于 docker ps ,查询资源列表 kubectl describe - 类似于 docker inspect ,获取资源的详细信息 kubectl logs - ...
- Linux知识积累(6) 系统目录及其用途
linux系统常见的重要目录以及各个目作用:/ 根目录.包含了几乎所有的文件目录.相当于中央系统.进入的最简单方法是:cd /./boot引导程序,内核等存放的目录.这个目录,包括了在引导过程中所必需 ...
- Spark入门(1-3)Spark的重要概念
1.什么是弹性分布式数据集? Spark提出了RDD(Resilient Distributed Datasets)这么一个全新的概念,RDD弹性分布式数据集是并行.容错的分布式数据结构:可以将RDD ...
- RxJava系列2(基本概念及使用介绍)
RxJava系列1(简介) RxJava系列2(基本概念及使用介绍) RxJava系列3(转换操作符) RxJava系列4(过滤操作符) RxJava系列5(组合操作符) RxJava系列6(从微观角 ...
- 浅谈移动端适配-rem
对于移动端开发来说,无可避免的就是直面各种设备不同分辨率和不同DPR(设备像素比)的问题,在此忽略其他兼容性问题的探讨. 一. 移动端开发有关于像素的概念: 1.设备像素(dp),也叫物理像素.指设备 ...
- 脱upx壳--初试--单步追踪
脱upx壳--初试--单步追踪 这里的练习题目是reversing.kr 的Easy Crack 我自己用upx加壳工具给它加了个壳,由于原文件逻辑简单,所以用它来练练手 之后用到的工具是IDA和Ol ...
- python模块之PIL模块
PIL简介 什么是PIL PIL:是Python Image Library的缩写,图像处理的模块.主要的类包括Image,ImageFont,ImageDraw,ImageFilter PIL的导入 ...
- typeof与instanceof的区别
一.instanceof运算符: 此运算符可以判断一个变量是否是某个对象(类)的实例,返回值是布尔类型的.想要理解它的作用,必须对面向对象有所理解: 代码实例如下: var str=new ...