原文:经典算法题每日演练——第七题 KMP算法

在大学的时候,应该在数据结构里面都看过kmp算法吧,不知道有多少老师对该算法是一笔带过的,至少我们以前是的,

确实kmp算法还是有点饶人的,如果说红黑树是变态级的,那么kmp算法比红黑树还要变态,很抱歉,每次打kmp的时候,输

入法总是提示“看毛片”三个字,嘿嘿,就叫“看毛片算法”吧。

一:BF算法

如果让你写字符串的模式匹配,你可能会很快的写出朴素的bf算法,至少问题是解决了,我想大家很清楚的知道它的时间复

杂度为O(MN),原因很简单,主串和模式串失配的时候,我们总是将模式串的第一位与主串的下一个字符进行比较,所以复杂

度高在主串每次失配的时候都要回溯,图我就省略了。

二:KMP算法

刚才我们也说了,主串每次都要回溯,从而提高了时间复杂度,那么能不能在“主串”和“模式串”失配的情况下,主串不回溯呢?

而是让”模式串“向右滑动一定的距离,对上号后继续进行下一轮的匹配,从而做到时间复杂度为O(M+N)呢?所以kmp算法就是

用来处理这件事情的,下面我们看下简单的例子。

通过这张图,我们来讨论下它的一般推理,假设主串为S,模式串为P,在Si != Pj的时候,我们可以看到满足如下关系式

Si-jSi-j+1...Sn-1=P0P1..Pj-1。那么模式P应该向右滑动多少距离?也就是主串中的第i个字符应与模式串中的哪一个字符进行比较?

假设应该与模式串中的第k的位置相比较,假如模式串中存在最大的前缀真子串和后缀真子串,那么有P0P1..Pk-1=Pj-kPj-k+1...Pj-1.

这句话的意思也就是说,在模式P中,前k个字符与j个字符之前的k个字符相同,比如说:“abad”的最大前缀真子串为“aba",最大

后缀真子串为“bad”,当然这里是不相等,这里的0<k<j,我们希望k接近于j,那么我们滑动的距离将会最小,好吧,现在我们用

next[j]来记录失配时模式串应该用哪一个字符于Si进行比较。

设 next[j]=k。根据公式我们有

-1        当j=0时

next[j] =   max{k| 0<k<j 且 P0P1..Pk-1=Pj-kPj-k+1...Pj-1}

0         其他情况

好,接下来的问题就是如何求出next[j],这个也就是kmp思想的核心,对于next[j]的求法,我们采用递推法,现在我们知道了

next[j]=k,我们来求next[j+1]=?的问题?其实也就是两种情况:

①:Pk=Pj 时  则P0P1...Pk=Pj-kPj-k+1...Pj, 则我们知:

next[j+1]=k+1。

又因为next[j]=k,则

next[j+1]=next[j]+1。

②:Pk!=Pj 时  则P0P1...Pk!=Pj-kPj-k+1...Pj,这种情况我们有点蛋疼,其实这里我们又将模式串的匹配问题转化为了上面我们提到

的”主串“和”模式串“中寻找next的问题,你可以理解成在模式串的前缀串和后缀串中寻找next[j]的问题。现在我们的思路就是一定

要找到这个k2,使得Pk2=Pj,然后将k2代入①就可以了。

设   k2=next[k]。 则有P0P1...Pk2-1=Pj-k2Pj-k2+1...Pj-1

若   Pj=Pk2,      则 next[j+1]=k2+1=next[k]+1。

若   Pj!=Pk2,      则可以继续像上面递归的使用next,直到不存在k2为止。

好,下面我们上代码,可能有点绕,不管你懂没懂,反正我懂了。

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text; namespace SupportCenter.Test
{
public class Program
{
static void Main(string[] args)
{
string zstr = "ababcabababdc"; string mstr = "babdc"; var index = KMP(zstr, mstr); if (index == -)
Console.WriteLine("没有匹配的字符串!");
else
Console.WriteLine("哈哈,找到字符啦,位置为:" + index); Console.Read();
} static int KMP(string bigstr, string smallstr)
{
int i = ;
int j = ; //计算“前缀串”和“后缀串“的next
int[] next = GetNextVal(smallstr); while (i < bigstr.Length && j < smallstr.Length)
{
if (j == - || bigstr[i] == smallstr[j])
{
i++;
j++;
}
else
{
j = next[j];
}
} if (j == smallstr.Length)
return i - smallstr.Length; return -;
} /// <summary>
/// p0,p1....pk-1 (前缀串)
/// pj-k,pj-k+1....pj-1 (后缀串)
/// </summary>
/// <param name="match"></param>
/// <returns></returns>
static int[] GetNextVal(string smallstr)
{
//前缀串起始位置("-1"是方便计算)
int k = -; //后缀串起始位置("-1"是方便计算)
int j = ; int[] next = new int[smallstr.Length]; //根据公式: j=0时,next[j]=-1
next[j] = -; while (j < smallstr.Length - )
{
if (k == - || smallstr[k] == smallstr[j])
{
//pk=pj的情况: next[j+1]=k+1 => next[j+1]=next[j]+1
next[++j] = ++k;
}
else
{
//pk != pj 的情况:我们递推 k=next[k];
//要么找到,要么k=-1中止
k = next[k];
}
} return next;
}
}
}

经典算法题每日演练——第七题 KMP算法的更多相关文章

  1. 经典算法题每日演练——第十七题 Dijkstra算法

    原文:经典算法题每日演练--第十七题 Dijkstra算法 或许在生活中,经常会碰到针对某一个问题,在众多的限制条件下,如何去寻找一个最优解?可能大家想到了很多诸如“线性规划”,“动态规划” 这些经典 ...

  2. 经典算法题每日演练——第十一题 Bitmap算法

    原文:经典算法题每日演练--第十一题 Bitmap算法 在所有具有性能优化的数据结构中,我想大家使用最多的就是hash表,是的,在具有定位查找上具有O(1)的常量时间,多么的简洁优美, 但是在特定的场 ...

  3. 经典算法题每日演练——第八题 AC自动机

    原文:经典算法题每日演练--第八题 AC自动机 上一篇我们说了单模式匹配算法KMP,现在我们有需求了,我要检查一篇文章中是否有某些敏感词,这其实就是多模式匹配的问题. 当然你也可以用KMP算法求出,那 ...

  4. 经典算法题每日演练——第六题 协同推荐SlopeOne 算法

    原文:经典算法题每日演练--第六题 协同推荐SlopeOne 算法 相信大家对如下的Category都很熟悉,很多网站都有类似如下的功能,“商品推荐”,"猜你喜欢“,在实体店中我们有导购来为 ...

  5. 经典算法题每日演练——第十一题 Bitmap算法 (转)

    http://www.cnblogs.com/huangxincheng/archive/2012/12/06/2804756.html 在所有具有性能优化的数据结构中,我想大家使用最多的就是hash ...

  6. 经典算法题每日演练——第十六题 Kruskal算法

    原文:经典算法题每日演练--第十六题 Kruskal算法 这篇我们看看第二种生成树的Kruskal算法,这个算法的魅力在于我们可以打一下算法和数据结构的组合拳,很有意思的. 一:思想 若存在M={0, ...

  7. 经典算法题每日演练——第十四题 Prim算法

    原文:经典算法题每日演练--第十四题 Prim算法 图论在数据结构中是非常有趣而复杂的,作为web码农的我,在实际开发中一直没有找到它的使用场景,不像树那样的频繁使用,不过还是准备 仔细的把图论全部过 ...

  8. 笔试算法题(52):简介 - KMP算法(D.E. Knuth, J.H. Morris, V.R. Pratt Algorithm)

    议题:KMP算法(D.E. Knuth, J.H. Morris, V.R. Pratt Algorithm) 分析: KMP算法用于在一个主串中找出特定的字符或者模式串.现在假设主串为长度n的数组T ...

  9. codeforces水题100道 第七题 Codeforces Round #270 A. Design Tutorial: Learn from Math (math)

    题目链接:http://www.codeforces.com/problemset/problem/472/A题意:给你一个数n,将n表示为两个合数(即非素数)的和.C++代码: #include & ...

随机推荐

  1. 初探ExtJS(2)

    二.建立第一个Demo.实现查数据库显示到页面 步骤:1.建立MySQL数据库表 2.整合SSH框架 3.用ExtJS显示 关键注意事项: Ext.data.JsonReader中root的含义,比如 ...

  2. LeetCode: Distinct Subsequences [115]

    [称号] Given a string S and a string T, count the number of distinct subsequences of T in S. A subsequ ...

  3. JAVA Socket超时浅析(转)

    套接字或插座(socket)是一种软件形式的抽象,用于表达两台机器间一个连接的“终端”.针对一个特定的连接,每台机器上都有一个“套接字”,可以想象它们之间有一条虚拟的“线缆”.JAVA有两个基于数据流 ...

  4. earlysuspend调用过程

    1. 电源管理的状态 Android的Linux内核为系统提供了4种电源状态,内核的源码为当中的3种定义了名字和相应的宏定义,名字定义在kernel/power/suspend.c中: constch ...

  5. cygwin,在win中开发linux程序

    cygwin,在win中开发linux程序 http://www.cygwin.cn/site/info/show.php?IID=1001  很多用windows的朋友不习惯于用linux的开发环境 ...

  6. hdu4190 简单的二分法

    题意是 有n个城市,m个投票箱.接下来n个城市人口数,每一个投票箱都不能为空.计算最后投票箱的容量必须达到多少,才干满足须要. 每一个城市的人必须仅仅能将票投到自己城市分得得投票箱中.要是容量最小箱子 ...

  7. ThinkPad E530 Fedora 20 无线上网问题

    它一直在使用 Fedora 家庭 Linux. 但它自带的无线网卡驱动似下一些问题,通常,有时连接,有时你不能. 经常搜索不到的家用无线路由器. 因为家里有网线所以一直没有在意.没什么事就折腾了一下. ...

  8. H. 硬币的水问题II

    H. 硬币水题II Time Limit: 1000ms Case Time Limit: 1000ms Memory Limit: 65536KB 64-bit integer IO format: ...

  9. MVC下判断用户登录和授权状态方法

    MVC下判断用户登录和授权状态方法 在我们日常开发的绝大多数系统中,都涉及到管理用户的登录和授权问题.登录功能(Authentication),针对于所有用户都开放:而授权(Authorization ...

  10. 每天进步一点点--&gt;函数fseek() 使用方法

    在阅读代码时,遇到了非常早之前用过的fseek(),非常久没实用了,有点陌生,写出来以便下次查阅. 函数功能是把文件指针指向文件的开头,须要包括头文件stdio.h fseek   函数名: fsee ...