零、先说点题外的吧
这一章学串,其中最经典的就是模式匹配的KMP算法。其实也算是巩固自己的知识,我把这一章的知识和zy顺了一遍,主要讲了KMP算法。大概讲了一个小时,讲完了之后,zy很兴奋的说了一句:感觉好神奇啊。很感动。感觉终于让一个没有领略过算法魅力的人感受到了算法的魅力,感觉她能从简单几行代码里“发现人类智慧居然如此璀璨”。
很遗憾。
我等你终有一天可以东山再起王者归来。
一、KMP思路
1.想要理解KMP,就要先理解最朴素的暴力算法
//此处省略N个字……不懂看书吧
2.KMP相当于对它的优化
暴力算法的复杂度是O(nm),即最坏情况下对每一个位置的主串数据遍历一遍模式串。其缺点在于,对主串数据重复遍历了很多遍,对模式串更是遍历了无数遍,尽管早就知道其中的数据了。
KMP的优化在于对其元素进行一次遍历,并把所需要的数据记录下来,避免以后重复遍历。
KMP算法的特点即:快速后移+决不后退(主串)!
二、主代码实现
//略……
注意了,在对这段主代码时,next表先不用理解,只要先讲其视作已知,将其每个位置理解为该后退到哪个位置即可。
三、next[]表的构造(关键)
1.next[j] 存的是该后退到的位置,即:模式串[0..j-1]中(或者说[0,j)中),匹配的模式串前缀和和主串后缀的最大长度。即模式串[0..j-1]与模式串[i-j,i-1]能匹配。
注意是模式串0..j-1而不是0..j!!!!!!!!!!!!!!!!!!!!!
2.当模式串前缀和后缀没有匹配,则next[j]=0,即长度为0,重新开始匹配。
3.next[0]=-1;
j指向的是 主串[i-j,i)与模式串[开头,j)匹配,所以next[0]=-1相当于在-1处引入一个不存在的哨兵,简化代码统一理解。将特殊情况转化为普通情况进行处理。同样可以将其视为一种标记,即第一个元素都不匹配时,标记j<0,然后重新处理。
4.next[]表的构造。即对模式串的每一个位置之前的子串进行自匹配。
分析:
看到网上有用数学归纳法来解释求解步骤。(数学归纳法有三个步骤,①初始条件成立②假设n=k情况成立③从n=k情况推导n=k+1的情况,若n=k+1的情况成立,则公式对自然数集成立。)其实感觉并不完全准确。这里用递推来说更准确一些。递推说的是,已知初始情况,则以后的任意n+1的情况均可以由n的情况推出。
只是应用到这个问题上求解步骤略微有些不同,递推的初始条件其实是很难界定的,我们不妨事后再进行考虑,我们可以先假设f(n)已知,然后考虑如何从n的情况推导出n+1的情况。
步骤:
(模式串此时视为主串P,指针为j;模式串的模式串此时称为次模式串C,指针为t)。
假设next[j]已知,注意是0..j-1 的最长前后缀已知,不是0..j
1)若P[j]==C[t] /* C[next[j]] */ 则并进且更新next表 t++;j++;next[j]=t;//区别,更新表
2)若P[j]!=C[t] 则次模式串指针后退且不更新next表(直至相等或者到头时进行更新)//非区别,后退不更新
3)重复上述步骤1)2)至next表更新至最后一个数据。
问题:
上述步骤留了一个问题,那就是初始条件。步骤2)中也存在一个简单问题,就是到底该退到什么时候?其实这是一个问题,那就是如何设置初始条件。
首先明确,当当前的序列无匹配的前后缀时,则应将next表在该位置设置为零。那么如何确定无匹配后缀呢?其实这还是如何设置初始条件的问题。说的再明确一点,那就是next[0]改如何设置的问题。那么该如何设置呢?其实这个问题在KMP主程序中说明过一遍。
因为next[j] 存的是模式串[0..j-1]中(或者说[0,j)中),匹配的模式串前缀和和主串后缀的最大长度。[0,-1]显然不好讨论,当然其实next[0]表示的其实也不是“存的是模式串[0..j-1]中(或者说[0,j)中),匹配的模式串前缀和和主串后缀的最大长度”next[0]表示的是当退到0的位置该如何处理,不妨先说一下next[1],按照上面的说法,next[1]应该表示将模式串[0..0](即模式串首元素)视作主串,此时的最大前缀,有人说这还用说嘛,肯定是1啊,如果真这么设定,那么我们假设一下,当P的某个字符和C[1]不相等时,指针t将退回next[t]即next[1],这样就相当于无变化,那该如何设置next[1]呢,想想此时该如何处理?其实是应该将j++,然后重新对j++之后的j与C[0]比较,为了使得这种特殊情况和普通情况应用步骤1)统一处理我们只需把指针t(此时t指向0)退一步,然后在j、t并进在比较即可,此时将t退一步,即假设有个t-1的哨兵,将t退到-1即可,用代码表示即为next[0]=-1。这样来看,next[0]虽然并不表示最长前后缀长度,但是还是表示“后退的位置”从这个语义上来说,其实next[0]与next表的其它值也保持了吻合。至于next[2]呢,next[2]可以通过next[1]用上述步骤推出啊……【笑哭】
好了,这样问题基本上就全部解决了。这样看来,KMP算法最重要的就是next表的实现啊。