应用:word编辑器里查找某个特定word的功能;DNA序列里查特定pattern。
m是pattern的长度,n是text的长度。
上面RK的matching时间是worst case,有点坑,但average case其实挺快的。
解决模式:沿着text T滑动pattern P, 用shift S来记录相对位移。
1. Naive的方法,是把s所有的值对应的情况都检查了一边,没有preprocess。之所以不够高效,是因为在处理shift = s+1时没有用上shift =s得到的信息。
2.Rabin-Karp-Matcher:
所有的character都对应成基数为d的digits(radix-d, d = 所用字母表的大小)。
把string P处理成integer p, 把shift = s时T上长度为m的子串处理成t(s),而且在t(s)的基础上计算t(s+1)。
这样preprocess的时间是Theta(m)。
处理成integer的问题:数太大了放不下 ==>那就把p和t(s)都mod q。
如果p mod q = t(s) mod q,那再进一步检查是不是p = t(s),否则肯定是p不等于t(s),叫spurious hit。
因为RK每个s的值都检查了,所以有(n-m+1)个loop,如果每个loop是spurious hit要继续深入检查,那就是O((n-m+1)*m)。
不然在valid hit比较少的情况下,average和expected time还是不错的,spurious hit发生的概率也是和q有关的。
3. Finite Automata有限自动机
自动机主要抓住,state(当前状态,即match了pattern的前多少个character)
和transition function(状态转移函数)这两个概念。
如果pattern的长度是m, 那么就有[0,...,m]共m+1个状态,其中0是初始状态,
m是accepting状态,表示pattern的每个character都已经匹配了。
转移函数theta(q,a) = sigma(Pq+"a"),其中q是当前的状态,如果此时input是a,那么找到一个string,
它是(P的前q位加上a)的后缀,同时也是P最长的前缀。
这个string的长度就是theta(q,a),也就是q要转移到的新状态。
当然如果P(q+1)也是a, 那么theta(q,a) = q+1(在还没有被accept的情况下)。
获得这个转移函数的过程就是preprocess的过程,如果有了的话,怎么用呢?
matching的时间负责度也就是Theta(n)。那preprocess的复杂度咋样呢?
4.KMP(好激动,终于到了看毛片算法)外面的loop是O(m*|Sigma|),里面可是O(m^2),当然可以优化,到O(m*|Sgima|) overall。
看毛片算法的主要精髓是:认清了问题的主要矛盾是pattern和自己的match。
在对text按位向后扫的时候,如果当前已经match的位数是k(k<m),那么这个T[s+1,...s+k],也就是目前T的suffix是确定的,就是pattern的前k位。
举个例子,如果T是;abcdfxxxxxx; P是:abcde。那我们在 k = 4,s=0的时候已经知道,如果k+1位match失败,s=1,2,3,4肯定是invalid。
所以我们要做的是,找到pattern的prefix function. 这个function可以分分钟生成有限状态机的转移矩阵的。
有了这个函数,我们可以recursive的找到valid的shift cases.
然后利用这个函数再次来扫Text, 就是KMP算法啦。
还有最后一个任务,就是KMP的算法复杂度分析。这里是用aggregate的方法。
先说prefix函数生成函数的复杂度,KMP的复杂度同理。
k从0起步,并一直保持小于m(根据prefix函数的定义),所以最大也就是m-1啦,increment总共的次数是O(m).
同时while循环的条件又保证了,k是non-negative的,所以k = pi(k)这种decrement函数,总共执行的次数也是O(m).
所以preprocess的时间复杂度就是O(m).
TBC:KMP和trie结合的AC自动机.