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

原文:经典算法题每日演练——第七题 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为止。

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

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5
 6 namespace SupportCenter.Test
 7 {
 8     public class Program
 9     {
10         static void Main(string[] args)
11         {
12             string zstr = "ababcabababdc";
13
14             string mstr = "babdc";
15
16             var index = KMP(zstr, mstr);
17
18             if (index == -1)
19                 Console.WriteLine("没有匹配的字符串!");
20             else
21                 Console.WriteLine("哈哈,找到字符啦,位置为:" + index);
22
23             Console.Read();
24         }
25
26         static int KMP(string bigstr, string smallstr)
27         {
28             int i = 0;
29             int j = 0;
30
31             //计算“前缀串”和“后缀串“的next
32             int[] next = GetNextVal(smallstr);
33
34             while (i < bigstr.Length && j < smallstr.Length)
35             {
36                 if (j == -1 || bigstr[i] == smallstr[j])
37                 {
38                     i++;
39                     j++;
40                 }
41                 else
42                 {
43                     j = next[j];
44                 }
45             }
46
47             if (j == smallstr.Length)
48                 return i - smallstr.Length;
49
50             return -1;
51         }
52
53         /// <summary>
54         /// p0,p1....pk-1         (前缀串)
55         /// pj-k,pj-k+1....pj-1   (后缀串)
56         /// </summary>
57         /// <param name="match"></param>
58         /// <returns></returns>
59         static int[] GetNextVal(string smallstr)
60         {
61             //前缀串起始位置("-1"是方便计算)
62             int k = -1;
63
64             //后缀串起始位置("-1"是方便计算)
65             int j = 0;
66
67             int[] next = new int[smallstr.Length];
68
69             //根据公式: j=0时,next[j]=-1
70             next[j] = -1;
71
72             while (j < smallstr.Length - 1)
73             {
74                 if (k == -1 || smallstr[k] == smallstr[j])
75                 {
76                     //pk=pj的情况: next[j+1]=k+1 => next[j+1]=next[j]+1
77                     next[++j] = ++k;
78                 }
79                 else
80                 {
81                     //pk != pj 的情况:我们递推 k=next[k];
82                     //要么找到,要么k=-1中止
83                     k = next[k];
84                 }
85             }
86
87             return next;
88         }
89     }
90 }

时间: 2024-08-04 09:01:07

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

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

原文:经典算法题每日演练--第十七题 Dijkstra算法         或许在生活中,经常会碰到针对某一个问题,在众多的限制条件下,如何去寻找一个最优解?可能大家想到了很多诸如"线性规划","动态规划" 这些经典策略,当然有的问题我们可以用贪心来寻求整体最优解,在图论中一个典型的贪心法求最优解的例子就莫过于"最短路径"的问题.   一:概序    从下图中我要寻找V0到V3的最短路径,你会发现通往他们的两点路径有很多:V0->V4-&g

经典算法题每日演练——第二十三题 鸡尾酒排序

原文:经典算法题每日演练--第二十三题 鸡尾酒排序     这篇我们继续扯淡一下鸡尾酒排序,为了知道为啥取名为鸡尾酒,特意看了下百科,见框框的话,也只能勉强这么说了.   要是文艺点的话,可以说是搅拌排序,通俗易懂点的话,就叫"双向冒泡排序",我想作为码农的话,不可能不知道冒泡排序, 冒泡是一个单向的从小到大或者从大到小的交换排序,而鸡尾酒排序是双向的,从一端进行从小到大排序,从另一端进行从大 到小排序. 从图中可以看到,第一次正向比较,我们找到了最大值9.             

经典算法题每日演练——第三题 猴子吃桃

原文:经典算法题每日演练--第三题 猴子吃桃           猴子第一天摘下若干个桃子,当即吃了一半,还不过瘾就多吃了一个.第二天早上又将剩下的桃子吃了一半,还是不过瘾又多 吃了一个.以后每天都吃前一天剩下的一半再加一个.到第10天刚好剩一个.问猴子第一天摘了多少个桃子?   分析: 这是一套非常经典的算法题,这个题目体现了算法思想中的递推思想,递归有两种形式,顺推和逆推,针对递推,只要         我们找到递推公式,问题就迎刃而解了.                令S10=1,容易看

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

原文:经典算法题每日演练--第六题 协同推荐SlopeOne 算法               相信大家对如下的Category都很熟悉,很多网站都有类似如下的功能,"商品推荐","猜你喜欢",在实体店中我们有导购来为我们服务,在网络上 我们需要同样的一种替代物,如果简简单单的在数据库里面去捞,去比较,几乎是完成不了的,这时我们就需要一种协同推荐算法,来高效的推荐浏览者喜 欢的商品. 一:概念      SlopeOne的思想很简单,就是用均值化的思想来掩盖个体的打

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

原文:经典算法题每日演练--第八题 AC自动机        上一篇我们说了单模式匹配算法KMP,现在我们有需求了,我要检查一篇文章中是否有某些敏感词,这其实就是多模式匹配的问题. 当然你也可以用KMP算法求出,那么它的时间复杂度为O(c*(m+n)),c:为模式串的个数.m:为模式串的长度,n:为正文的长度,那 么这个复杂度就不再是线性了,我们学算法就是希望能把要解决的问题优化到极致,这不,AC自动机就派上用场了.    其实AC自动机就是Trie树的一个活用,活用点就是灌输了kmp的思想,从

经典算法题每日演练——第十题 树状数组

原文:经典算法题每日演练--第十题 树状数组         有一种数据结构是神奇的,神秘的,它展现了位运算与数组结合的神奇魅力,太牛逼的,它就是树状数组,这种数据结构不是神人是发现不了的. 一:概序      假如我现在有个需求,就是要频繁的求数组的前n项和,并且存在着数组中某些数字的频繁修改,那么我们该如何实现这样的需求?当然大家可以往 真实项目上靠一靠. ① 传统方法:根据索引修改为O(1),但是求前n项和为O(n). ②空间换时间方法:我开一个数组sum[],sum[i]=a[1]+..

经典算法题每日演练——第四题 最长公共子序列

一: 作用        最长公共子序列的问题常用于解决字符串的相似度,是一个非常实用的算法,作为码农,此算法是我们的必备基本功. 二:概念      举个例子,cnblogs这个字符串中子序列有多少个呢?很显然有27个,比如其中的cb,cgs等等都是其子序列,我们可以看出 子序列不见得一定是连续的,连续的那是子串.      我想大家已经了解了子序列的概念,那现在可以延伸到两个字符串了,那么大家能够看出:cnblogs和belong的公共子序列吗? 在你找出的公共子序列中,你能找出最长的公共子

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

     在所有具有性能优化的数据结构中,我想大家使用最多的就是hash表,是的,在具有定位查找上具有O(1)的常量时间,多么的简洁优美, 但是在特定的场合下: ①:对10亿个不重复的整数进行排序. ②:找出10亿个数字中重复的数字. 当然我只有普通的服务器,就算2G的内存吧,在这种场景下,我们该如何更好的挑选数据结构和算法呢?   一:问题分析      这年头,大牛们写的排序算法也就那么几个,首先我们算下放在内存中要多少G: (10亿 * 32)/(1024*1024*1024*8)=3.6

经典算法题每日演练——第五题 字符串相似度

        这篇我们看看最长公共子序列的另一个版本,求字符串相似度(编辑距离),我也说过了,这是一个非常实用的算法,在DNA对比,网 页聚类等方面都有用武之地. 一:概念      对于两个字符串A和B,通过基本的增删改将字符串A改成B,或者将B改成A,在改变的过程中我们使用的最少步骤称之为"编辑距离". 比如如下的字符串:我们通过种种操作,痉挛之后编辑距离为3,不知道你看出来了没有? 二:解析   可能大家觉得有点复杂,不好理解,我们试着把这个大问题拆分掉,将"字符串