本文只是随手记录。在网上翻东西的时候就经常会看到一些没听过或者听过但没去了解的事情。有些时候刚好有心情了,就去了解。了解完又容易忘记。就像之前弄的全连通子图,过了一段时间了,不仅算法名字忘了,连到底是怎么做的我都想不起来了(逃。所以这次还是干脆写下来好了。
Boyer-Moore是一个字符串算法,这个算法追求的就是,每次匹配了一般发现失败了,要往前移动尽可能多的距离,少算一点是一点。为了实现这个目标,首先算法选择的就是从pattern的尾部开始算。这个时候就会出现若干种情况。
第一种,第一个字符匹配就失败了,而且这个字符没有在pattern里面出现过。
Google Removes 'Don't Be Evil' Clause From Its Code of Conduct
Evil
我们要在这句话里面找Evil。首先我们比较Goog和Evil,从字符串的头部,但是从pattern的尾部开始。显然第一次比较g和l就失败了。而且我们发现,g并没有出现在Evil里面。怎么办?直接就挪动4个字符(Evil的长度),变成这样。
Google Removes 'Don't Be Evil' Clause From Its Code of Conduct
Evil(R)
Evil(v)
跑的比谁都快。
第二种,第一个字符匹配就失败了,而且这个字符在pattern里面出现了。
还是刚才那个例子,我们挪动了两次,终于遇到了emov和Evil。v和l显然是不一样的,但是v在Evil里面出现过。怎么办?我们要做的就是对齐v字符,然后就变成这样:
Google Removes 'Don't Be Evil' Clause From Its Code of Conduct
Evil(s)
Evil(o)
Evil( )
Evil(E!)
Evil(yes!)
如果这个字符在pattern里面出现了多次,就取最后一个。进步太激进恐生变故也。然后就继续找,果不其然找到了。
这个时候我们进步了7次,一共才比了7+4个字符,太强了。不过这个例子还是过于完美了,我们再找新的。
第三种,匹配了一个后缀成功,而且这个后缀在pattern里的其他地方出现了。
这算是比较常见的情况。这次我们换个例子
Exclusive: Huawei registers “HongMeng” trademark all around the globe
HongMeng
我们还是尝试匹配一下这个字符串
Exclusive: Huawei registers “HongMeng” trademark all around the globe
HongMeng(v)
HongMeng(e)
HongMeng( )
HongMeng(r)
Hongmeng(ong)
为了让Hongmeng对齐“ “Hong”,我决定用一个老梗:汉字占两个字节(逃
现在我们发现,Hongmeng的ng对齐了,但是ong和eng显然不一样。我们成功匹配了ng后缀,而且这个ng在Hongmeng里面出现了两次。这个时候我们就要对齐ng。我们用倒数第二个ng来跟字符串里的ng对齐,变成这样:
Exclusive: Huawei registers “HongMeng” trademark all around the globe
HongMeng(yes!)
马上就成功了!这个时候我们进步了5次,一共才比了7+8个字符,太强了。不过这个例子说不定还是运气爆炸了,我们再换一个例子。
第四种,匹配了一个后缀成功,这个后缀在pattern里面只出现了一次。
The stress of chronic illness can also cause sleeplessness and daytime drowsiness
sleeplessness(f)
sleeplessness(n)
sleeplessness(lness)
我们想在这句话里面找sleeplessness,倒是找到了illness的后缀ness。我们发现,ness虽然是一个后缀,但是他只出现了一次。接下来怎么办呢?我们能不能退而求其次找倒数第二个ess呢?其实是不行的。因为ness只出现了一次,证明任何地方的ess你就算对齐了,他都不可能匹配成功,因为ess前面不是n。如果是,那么就跟“ness只出现一次”这个事实不吻合。
下面只有若干种细分可能:
- 尽管ness匹配成功了,但是ness只出现了一次,可是ess、ss和s也一样只出现了一次。那这个没得说了,直接前进13个字符(sleeplessness的长度)
- ness匹配成功了,但是我们找到了ness后缀的另一个后缀s,他出现在字符串的开始。这个是符合我们sleepnessness这个pattern的情况的。这个就好说了,对齐这个s后缀。
为什么后缀的后缀出现在开始就可以对齐呢?显然头部前面的字符是什么都可以。出现在中间不行,是因为对其了也会导致n不能对齐。现在我们把ness的nes对齐到了字符串的前面出去了,当然是可能找到一个match的:
The stress of chronic illness can also cause sleeplessness and daytime drowsiness
sleeplessness(a)
sleeplessness(ess)
sleeplessness(yes!)
这么长的一句话,我们进步了5次,一共才比了11+13个字符,太强了。
尾声
当然这个算法到这里还没完。如果以上四种情况的多种同时出现了怎么办?我们就都计算一下各自进步了多少个字符,然后取最长的那个就好了。真是跑的比谁都快。
剩下的最后一个问题,就是怎么具体计算出前进多少个字符的这个多少。大家仔细想一下就知道,这个“多少”,其实只取决于匹配到的字符串后缀的长度还有pattern自己,至于输入是什么根本无所谓。于是我们可以针对pattern来生成表,然后查表就好了。
表一共有两张。第一张是针对匹配了0长度后缀的,根据失败的那个输入的字符是什么,决定跳多少字符。第二张是针对匹配了n长度后缀的。我们根据n的具体数字,就可以直接决定跳多少字符了。大家想一下是不是这样?这样每次比较到失败之后,O(1)就可以马上决定前进多少。
而且就算你的字符串不是1字节也无所谓,我们仍然当字节来匹配,制作进步表的时候把必须进步sizeof(字符)倍数长度的这个因素考虑进去就可以了,第一张表是不会膨胀的。