浅谈kmp

一、废话不多说,首先直白的说kmp是什么?

  先给你两个字符串,一个长的串一个短的串。 例:  abcdabccabca 与  cca,然后需要你求出第二个字符串第一个字符串中的位置

暴力匹配的话大概就是下文的算法

 1 for( int i =0 ; i < str1len ; i++){
 2        int flag=1;
 3     for( int j = 0 ; j < str2len ; j++){
 4         if( str1[ i+j ] != str2[ j ]{
 5             flag=0;
 6             break;
 7         }
 8     }
 9     if(flag)printf("%d ",i);
10 }
11 //strl1len 指length of string 1 也就是字符串1的长度。
12 //str1 指第一个字符串
13 //通常,在字符串匹配中,作者,默认:长串:str1,短串:str2

这种算法是大多数人都能想到的,时间复杂度约为O(mn)mn分别是两个字符串长度。

  而kmp算法就是针对这种串匹配问题的一种优化算法。

叫kmp的原因是因为发明这个算法的人三个巨巨名字首字母是分别kmp

继续读下去之前,请先确保对暴力匹配的算法进行理解。

二、为什么用kmp?

  kmp算法的核心在于对已匹配的信息充分利用。好吧,这么说太抽象了,举个具体的栗子吧。

  假设我们的第二个字符串是这样的。

  

  在与第一个字符串比较的时候,我们分别有两个字母, i代表在字符串1中匹配当前起始位置,j代表字符串2中匹配的位置。也就是上面暴力匹配代码中的str1 [ i+j ] == str2[ j ];

  现在假设我们字符串2中匹配到了这个位置

 

  然后检测到不同,那么我们是不是应该将j回到头呢?不是的。

  红色划线部分是相同的对吧。这也就是意味着,如果我们j匹配到箭头所指的abc

  那么在字符串1中的i+j-1 i+j-2 i+j-3这三个元素一定也是abc。为什么? 因为我匹配过了啊。这就是我们所得到的已知信息。

  我们还已知什么呢?

  字符串2中的头三个字母 和目前失配的前三个字母是相同的。于是乎,我们可以把j移到前三个abc的后面也就是下图

  为什么?  因为你匹配到的当前后缀与str2的前缀有公共部分。所以匹配时,这些公共部分就可以当做匹配过。

  (后缀:就是从后往前的字符串,比如这个就是 a,ca,bca,直白的说就是后几个)

  (前缀:从前往后的字符串,比如a,ab,abc,直白的说就是前几个)

  这么做的好处是i每次到可以每次更新到i+j,不用回溯了。每次i只管往前走就行,j自己玩去。我们就省了很多的回溯时间。

  那么你一定又有一个疑问了。该怎么做呢?我怎么知道回到哪去呢???

三、怎么整这个算法啊??

  向下阅读请先对上方原理进行理解。

  那么我们可以着手上方的问题了,怎么知道可以回到哪去呢?

从下文起,i指目前匹配到的所在str1的位置, j 指目前匹配到 所在str2 的位置

  我们可以开出一个数组,在匹配前先对str2的每一位进行计算,也就是如果是第一位失配我该回到哪?第二位失配我该回到哪?and so on...

  读者在此像大部分kmp教程一样,将这个(对应了str2中,解释每一位该回到前面哪里去,也可以说是前缀和后缀的最长相同区域的)数组称作 next【】数组。

  比如还是上面那个字符串

  

  我们要求出他的每一位

  如果我们第一位失配,好吧我们别无选择,i进行下一位匹配吧,j不变。

  如果第二位失配,我们可以得到信息,b ,和前缀a不同。我们j回到最初的起点,(i到下一位)。

  如果第三位失配,我们已知信息中,第二位头不是a,所以无法匹配,所以i可以继续下一位。

  也就是说

  next[1] next[2] next[3]都是0, 直接回到了0处。

  以此类推,我们可以得到next数组为{0,0,0,0,0,1,2,3,}

  为了检测 j 在0点失配,我们规定 next[0]=-1;

  同时我们也可以发现,在求next数组时如果前一位最长前后缀是a,后一位也与前一位最长前缀的下一位匹配了,那么它的最长前后缀显然是a+1;

  比如 abcdab时,最长前后缀是2 当到下一位 abcdabc时,多出的c这一位与前一位前缀最后一位(绕的有点晕).

  就是说 abcdab 中的 前缀abc只有ab与后缀 dab的ab匹配了,那么  前缀ab的后一位c 与后缀的多出的一位c匹配, 这个多出的位最长公共前后缀就是比前一位多一位。

好了,这就是next数组的求法,下面给出代码。

void get_next()
{
    int i = 0, j = -1;
    nexxt[0] = -1;                         //对第一位初始化为-1;
    while (str2[i] != ‘\0‘)
       {
        while (j != -1 && str2[i] != str2[j])//如果前后缀不相同了,
            j = nexxt[j];                      //看上一个最长前后缀

                nexxt[++i] = ++j;            //更新当前前后缀。
    }
}

有了这个数组,使用原理在第二部分也说过了,那么直接上使用的代码吧。

int kmp_1()
{
    int i=0,j=0;                              //各自从各自数组起点出发
    while(str1[i]!=‘\0‘)                   //检测字符串1是否到尾部
    {
        if(j==-1||str1[i] == str2[j] ){ //如果匹配,或者j在头开始的
            if(str2[j+1]==‘\0‘)          //检测字符串2是否到尾,到了就说明
            {                                   //已经完全匹配了
                return i-j+1;              //返回字符串2在1 的位置
            }
            else j++;                      //没到头就继续匹配下一位
            i++;
        }
        else j=nexxt[j];                //j自己向前走,直到有公共前后缀,或
    }                                         //回到0处。

    return -1;                       //str1都匹配完了还没找到,只好返回-1咯
}

!!!注意:以上检测代码尾部的时候,若对时间要求高,则需要将检测尾部的

str1[i]!=‘\0‘  和   str2[j+1]==‘\0‘  提前将两个字符串长度存下来,也就是 i<str1len   j+1<str2len,否则会像我一样超时到自闭


四、完整代码

  以下代码可以直接当做模板

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 using namespace std;
 5 int nexxt[100000005];
 6 char str2[100000005];
 7 char str1[100000005];
 8 int str1len, str2len;
 9 void get_next()
10 {
11     int i = 0, j = -1;
12     nexxt[0] = -1;
13     while (i < str2len) {
14         while (j != -1 && str2[i] != str2[j])
15             j = nexxt[j];
16         nexxt[++i] = ++j;
17     }
18 }
19 int kmp_1()
20 {
21     int i = 0, j = 0;
22     while (i < str1len)
23     {
24         if (j == -1 || str1[i] == str2[j]) {
25             j++;
26             i++;
27             if (j == str2len)return i - j + 1;
28         }
29         else j = nexxt[j];
30     }
31     return -1;
32 }
33 int main()
34 {
35     int n, a, b;
36     cin >> n;
37     while (n--)
38     {
39         getchar();
40         cin.getline(str1, 100000000, ‘\n‘);
41         cin.getline(str2, 100000000, ‘\n‘);
42         str1len = strlen(str1);
43         str2len = strlen(str2);
44         get_next();
45         cout << kmp_1() << endl;
46     }
47     return 0;
48 }

  

  

原文地址:https://www.cnblogs.com/greenpepper/p/10728147.html

时间: 2024-10-18 04:23:31

浅谈kmp的相关文章

【ZOJ】3785 What day is that day? ——浅谈KMP应用之ACM竞赛中的暴力打表找规律

首先声明一下,这里的规律指的是循环,即找到最小循环周期.这么一说大家心里肯定有数了吧,“不就是next数组性质的应用嘛”. 先来看一道题 ZOJ 3785 What day is that day? Time Limit: 2 Seconds      Memory Limit: 65536 KB It's Saturday today, what day is it after 11 + 22 + 33 + ... + NN days? Input There are multiple tes

浅谈KMP算法及其next[]数组

KMP算法是众多优秀的模式串匹配算法中较早诞生的一个,也是相对最为人所知的一个. 算法实现简单,运行效率高,时间复杂度为O(n+m)(n和m分别为目标串和模式串的长度),比蛮力算法的O(nm)快了许多. 理解KMP算法,关键是理解其中的精髓——next[]数组. (统一起见,下文将目标字符串记作obj,将模式字符串记作pattern,这与后面的程序代码是一致的) 我们给一个字符串S定义一个next值,记作next(S),next(S)=n表示: (1)S的前n个字符构成的前缀,和后n个字符的后缀

单模式串匹配----浅谈kmp算法

模式串匹配,顾名思义,就是看一个串是否在另一个串中出现,出现了几次,在哪个位置出现: p.s.  模式串是前者,并且,我们称后一个 (也就是被匹配的串)为文本串: 在这篇博客的代码里,s1均为文本串,s2均为模式串: 一般地,文本串长度不小于匹配串:(否则无意义) 很显然可以得到一个暴力的做法 : for i : 1~lenth_of_s1 {//枚举匹配串在文本串中的开始位置 for j : 1~lenth_of_s2 if(s2[j]!=s1[i+j-1]) break; if j>lent

Kmp算法浅谈

Kmp算法浅谈 一.Kmp算法思想 在主串和模式串进行匹配时,利用next数组不改变主串的匹配指针而是改变模式串的匹配指针,减少大量的重复匹配时间.在Kmp算法中,next数组的构建是整个Kmp算法的核心所在. 二.Kmp核心之next数组的构建 (1)前缀,后缀的定义 (2)最长公共前后缀定义 (3)next数组的含义 next数组代表各个长度子串(这些字串的起始位置都为0)的最长公共前后缀长度,为了方便代码的编写next[0]定为-1,表示前0个字符根本不存在前后缀这一说法.其他的例如nex

.net中对象序列化技术浅谈

.net中对象序列化技术浅谈 2009-03-11 阅读2756评论2 序列化是将对象状态转换为可保持或传输的格式的过程.与序列化相对的是反序列化,它将流转换为对象.这两个过程结合起来,可以轻松地存储和传输数 据.例如,可以序列化一个对象,然后使用 HTTP 通过 Internet 在客户端和服务器之间传输该对象.反之,反序列化根据流重新构造对象.此外还可以将对象序列化后保存到本地,再次运行的时候可以从本地文件 中“恢复”对象到序列化之前的状态.在.net中有提供了几种序列化的方式:二进制序列化

浅谈——页面静态化

现在互联网发展越来越迅速,对网站的性能要求越来越高,也就是如何应对高并发量.像12306需要应付上亿人同时来抢票,淘宝双十一--所以,如何提高网站的性能,是做网站都需要考虑的. 首先网站性能优化的方面有很多:1,使用缓存,最传统的一级二级缓存:2,将服务和数据库分开,使用不同的服务器,分工更加明确,效率更加高:3,分布式,提供多台服务器,利用反向代理服务器nginx进行反向代理,将请求分散开来:4,数据库的读写分离,不同的数据库,将读操作和写操作分开,并实时同步即可:5,分布式缓存,使用memc

单页应用SEO浅谈

单页应用SEO浅谈 前言 单页应用(Single Page Application)越来越受web开发者欢迎,单页应用的体验可以模拟原生应用,一次开发,多端兼容.单页应用并不是一个全新发明的技术,而是随着互联网的发展,满足用户体验的一种综合技术. SEO 一直以来,搜索引擎优化(SEO)是开发者容易忽略的部分.SEO是针对搜索(Google.百度.雅虎搜索等)在技术细节上的优化,例如语义.搜索关键词与内容相关性.收录量.搜索排名等.SEO也是同行.市场竞争常用的的营销手段.Google.百度的搜

浅谈html标签

浅谈html各常用标签用法 标题标签:<h1>-<h6>来表示,使标题字体变粗. <br />换行标记 <hr />水平分隔符 &nbsp空格符 &copy版权符 <a href>a标签超链接 href可接链接地址 <p>段落标签<blockquote>引用标签及可用做缩进 <table>表格中的<ul>无序列表<ol>有序列表<dl>自定义列表<row

浅谈二维中的树状数组与线段树

一般来说,树状数组可以实现的东西线段树均可胜任,实际应用中也是如此.但是在二维中,线段树的操作变得太过复杂,更新子矩阵时第一维的lazy标记更是麻烦到不行. 但是树状数组在某些询问中又无法胜任,如最值等不符合区间减法的询问.此时就需要根据线段树与树状数组的优缺点来选择了. 做一下基本操作的对比,如下图. 因为线段树为自上向下更新,从而可以使用lazy标记使得矩阵的更新变的高校起来,几个不足就是代码长,代码长和代码长. 对于将将矩阵内元素变为某个值,因为树状数组自下向上更新,且要满足区间加法等限制