KMP串匹配算法解析与优化

朴素串匹配算法说明

串匹配算法最常用的情形是从一篇文档中查找指定文本。需要查找的文本叫做模式串,需要从中查找模式串的串暂且叫做查找串吧。

为了更好理解KMP算法,我们先这样看待一下朴素匹配算法吧。朴素串匹配算法是这样的,当模式串的某一位置失配时将失配位置的上一位置与查找串的该位置对齐再从头开始比较模式串的每一个位置。如下图所示。

KMP串匹配算法解析

KMP串匹配算法是Knuth-Morris-Pratt算法的简称,KMP算法的思想就是当模式串的某一位置失配时,能不能将更前面的位置与查找串的该位置对齐,并且直接从该位置开始比较。按照这个思路走,问题叫变成了:当模式串的某一位置失配时要找到一个更前面的位置与查找串的该位置对齐。模式串的某个位置失配时的这个更前面的位置就叫做回溯位,通常用next表示,它的计算公式是:

next[i]= { 0; 当 i = 1
                        k; 对于串P,存在1 <= k < 使得 P1..Pk-1 == Pi-k+1..Pi-1
                        1; 其他情况                                }

这个公式对应的串的下标是从1开始的。这个公式只说明:模式串中某一位置(不包含此位置)之前部分具有首尾相同的子串(即自匹配,比如ABCABA最后一个A之前头部和尾部都包含了子串AB)时,如果该位置失配可以直接将头部子串的下一个位置和该处对齐(比如模式串ABCABA在最后一个A处失配可以直接滑动模式串将C对齐原来最后那个A对齐的位置),这样可以去掉模式串在某位置失配时该位置之前的子串在朴素匹配算法中存在的冗余比较(如果用朴素匹配算法,需要将模式串ABCABA移动三次才能使得C对齐原来最后那个A对齐的位置)。模式串中某一位置(不包含此位置)之前部分不具有首尾相同的子串时,在该位置失配时可以直接让模式串的开始位置对齐该位置。如下图。

这里只给出了算法的说明,但是如何能够证明算法是正确的呢?这个说麻烦也麻烦,说简单也简单。为什么麻烦呢?因为我没办法用形式化的语言给出证明过程,就像数学里面的证明过程一样。其实自己通过形象思维演示一下串匹配的滑动过程就能够相信这个算法肯定是正确的。我也懒得给出证明过程。

接下来给出KMP算法的完整代码。

#include <iostream>
#include <iomanip>
#include <vector>
#include <string>
#include <cstdlib>

using namespace std;

void get_next(const string & M,vector<int> & next);

int  KMP_match(const string & S,const string & M,int pos);

int main( )
{
    string S="abcdefghabcdefghhiijiklmabc";
    string T="hhiij";
    int    pos =KMP_match(S,T,3);

    cout<<"\n"<<pos<<endl;
    system("pause");
    return 0;
}

void get_next(const string & M,vector<int> & next)
{
    //按模式串生成vector<int> next(M.size(),-1);
    //这里的串的第1个元素下标是0
    int i = -1, j = 0;
    int M_len = M.size()-1;
    do
    {
     if((i < 0) || (M[i] == M[j]))
     {
      i++; j++;
      next[j] = i;
     }
        else
      i = next[i];
  cout<<"i="<<right<<setw(3)<<i
            <<"  j="<<right<<setw(3)<<j
            <<"  next["<<j<<"] ="<<right<<setw(3)<<next[j]<<endl;
     }while( j < M_len);
}

int  KMP_match(const string & S,const string & M,int pos)
{
    int j = pos, i = 0;//这里的串的第1个元素下标是0
    int S_len = S.size();
    int M_len = M.size();

    if((S_len-pos) < M_len)
        return -1;

    vector<int> next(M.size(),-1);

    get_next(M,next);

    while (i<M_len && j<S_len)
    {
        if (i < 0 || S[j]==M[i])
        {
            ++i;
            ++j;
        }
        else i = next[i];//j不变,i跳动
    }   

    if (i == M_len) return j-i;//匹配成功
    else return -1;
}

KMP串匹配算法的优化

接着看上面EBAEB的匹配例子。其中第2次比较根本没有必要,可以直接跳到第3次。这次比较有一个特点:当模式串滑过一段距离后模式串中参与比较的字符和前次参与比较的字符相同,都是B。按照上面那个公式求上述模式串回溯位置时的一个情形如下:

此时确定的是模式串中索引为4的元素失配时模式串的回溯位置,这时索引位下一个元素和比较位的下一个元素相同(都为B)。同时这也是模式串中索引为4的字符失配后滑动完成的情形,即最后一个B和查找串种的D不匹配时,由于next[4] = 1,需要把索引为1的字符(就是第2个字符)B对其到D。但是这个B和D是不是已经比较过一次了啊。这是因为不仅模式串失配位置之前的部分能够自匹配,而且模式串中包含失配位置的之前部分也能自匹配。模式串中包含失配位置的之前部分组成的子串也具有相同的首尾时,失配位置的回溯位置可以直接采用首部字串的回溯位置,对于串EBAEB,可以让next[4]直接等于next[1]。那么优化的next函数如下:

             next[i]= { 0; 当i = 1
                        k; 对于串P,存在1 <= k < 使得 P1..Pk-1 == Pi-k+1..Pi-1 & Pk != Pi
                        next[k]; 对于串P,存在 1 <= k < i使得 P1..Pk == Pi-k..Pi
                        1; 其他情况                                      }

接下来给出改进后的就next数组的代码。

void get_next(const string & M,vector<int> & next)
{
    //按模式串生成vector<int> next(M.size(),-1);
    //这里的串的第1个元素下标是0
    int i = -1, j = 0;
    int M_inx = M.size() - 1;

    do
    {
     if((i < 0) || (M[i] == M[j]))
     {
      i++; j++;
                if(M[i] != M[j])
          next[j] = i;
                else  next[j] = next[i];
 }
        else
  i = next[i];
     }while( j < M_inx);
}

时间: 2024-11-07 20:07:48

KMP串匹配算法解析与优化的相关文章

KMP算法解析(转自图灵社区)

KMP算法是一个很精妙的字符串算法,个人认为这个算法十分符合编程美学:十分简洁,而又极难理解.笔者算法学的很烂,所以接触到这个算法的时候也是一头雾水,去网上看各种帖子,发现写着各种KMP算法详解的转载帖子上面基本都会附上一句:“我也看的头晕”——这种诉苦声一片的错觉仿佛人生苦旅中找到知音,让我几乎放弃了这个算法的理解,准备把它直接记在脑海里了事. 但是后来在背了忘忘了背的反复过程中发现一个真理:任何对于算法的直接记忆都是徒劳无功的,基本上忘得比记的要快.后来看到刘未鹏先生的这篇文章:知其所以然(

地理围栏算法解析(Geo-fencing)

地理围栏算法解析 http://www.cnblogs.com/LBSer/p/4471742.html 地理围栏(Geo-fencing)是LBS的一种应用,就是用一个虚拟的栅栏围出一个虚拟地理边界,当手机进入.离开某个特定地理区域,或在该区域内活动时,手机可以接收自动通知和警告.如下图所示,假设地图上有三个商场,当用户进入某个商场的时候,手机自动收到相应商场发送的优惠券push消息.地理围栏应用非常广泛,当今移动互联网主要app如美团.大众.手淘等都可看到其应用身影. 图1 地理围栏示意图

区块链~Merkle Tree(默克尔树)算法解析~转载

转载~Merkle Tree(默克尔树)算法解析 /*最近在看Ethereum,其中一个重要的概念是Merkle Tree,以前从来没有听说过,所以查了些资料,学习了Merkle Tree的知识,因为接触时间不长,对Merkle Tree的理解也不是很深入,如果有不对的地方,希望各位大神指正*/ Merkle Tree概念 Merkle Tree,通常也被称作Hash Tree,顾名思义,就是存储hash值的一棵树.Merkle树的叶子是数据块(例如,文件或者文件的集合)的hash值.非叶节点是

程序员收藏必看系列:深度解析MySQL优化(二)

程序员收藏必看系列:深度解析MySQL优化(一) 性能优化建议 下面会从3个不同方面给出一些优化建议.但请等等,还有一句忠告要先送给你:不要听信你看到的关于优化的“绝对真理”,包括本文所讨论的内容,而应该是在实际的业务场景下通过测试来验证你关于执行计划以及响应时间的假设. scheme设计与数据型优化选择数据类型只要遵循小而简单的原则就好,越小的数据类型通常会更快,占用更少的磁盘.内存,处理时需要的CPU周期也更少.越简单的数据类型在计算时需要更少的CPU周期,比如,整型就比字符操作代价低,因而

高斯模糊算法的全面优化过程分享(二)。

      相关链接: 高斯模糊算法的全面优化过程分享(一) 在高斯模糊算法的全面优化过程分享(一)一文中我们已经给出了一种相当高性能的高斯模糊过程,但是优化没有终点,经过上一个星期的发愤图强和测试,对该算法的效率提升又有了一个新的高度,这里把优化过程中的一些心得和收获用文字的形式记录下来. 第一个尝试   直接使用内联汇编替代intrinsics代码(无效) 我在某篇博客里看到说intrinsics语法虽然简化了SSE编程的难度,但是他无法直接控制XMM0-XMM7寄存器,很多指令中间都会用内

[转]SURF算法解析

SURF算法解析 一.积分图像    积分图像的概念是由Viola和Jones提出的.积分图像中任意一点(i,j)的值为原图像左上角到任意点(i,j)相应的对焦区域的灰度值的总和,其数学公式如图1所示: 那么,当我们想要计算图片一个区域的积分,就只需计算这个区域的四个顶点在积分图像里的值,便可以通过2步加法和2步减法计算得出,其数学公式如下: 二.Hession矩阵探测器1.斑点检测    斑点:与周围有着颜色和灰度差别的区域.    在一个一维信号中,让它和高斯二阶导数进行卷积,也就是拉普拉斯

Android中锁屏密码算法解析以及破解方案

一.前言 最近玩王者荣耀,下载了一个辅助样本,结果被锁机了,当然破解它很简单,这个后面会详细分析这个样本,但是因为这个样本引发出的欲望就是解析Android中锁屏密码算法,然后用一种高效的方式制作锁机恶意样本.现在的锁机样本原理强制性太过于复杂,没意义.所以本文就先来介绍一下android中的锁屏密码算法原理. 二.锁屏密码方式 我们知道Android中现结单支持的锁屏密码主要有两种: 一种是手势密码,也就是我们常见的九宫格密码图 一种是输入密码,这个也分为PIN密码和复杂字符密码,而PIN密码

Mmseg中文分词算法解析

@author linjiexing 开发中文搜索和中文词库语义自己主动识别的时候,我採用都是基于mmseg中文分词算法开发的Jcseg开源project.使用场景涉及搜索索引创建时的中文分词.新词发现的中文分词.语义词向量空间构建过程的中文分词和文章特征向量提取前的中文分词等,整体使用下来,感觉jcseg是一个非常优秀的开源中文分词工具,并且可配置和开源的情况下,能够满足非常多场景的中文分词逻辑.本文先把jcseg使用到最主要的mmseg算法解析一下. 1. 中文分词算法之争 在分析mmseg

mwc飞控PID算法解析

0.说明 基于mwc2.3的pid算法解析,2.3中增加了一种新的pid算法,在此分别解析. P:比例 I:积分 D:微分 1.老版PID代码 代码大概在MultiWii.cpp的1350行上下. 1 if ( f.HORIZON_MODE ) prop = min(max(abs(rcCommand[PITCH]),abs(rcCommand[ROLL])),512); 2 3 // PITCH & ROLL 4 for(axis = 0; axis < 2; axis++) { 5 rc