Boyer-Moore良好后缀启发式

时间:2013-10-13 12:31:35

标签: c algorithm boyer-moore

我理解不良角色的启发式工作原理。当您找到不匹配的字母x时,只需移动模式,使模式中最右侧的x与字符串中的x对齐。并且它很容易在代码中实现。

我想我也明白了后缀启发式的工作方式。当我们找到一个好的后缀s时,在模式中的不同位置找到相同的后缀并移动它,以便模式中的s与字符串中的s对齐。但我不明白如何在代码中这样做。我们如何找到模式中另一个地方是否存在相同的后缀?我们怎么知道它的位置?代码:

void bmPreprocess1()
{
    int i=m, j=m+1;
    f[i]=j;
    while (i>0)
    {
        while (j<=m && p[i-1]!=p[j-1])
        {
            if (s[j]==0) s[j]=j-i;
            j=f[j];
        }
        i--; j--;
        f[i]=j;
    }
}
来自http://www.iti.fh-flensburg.de/lang/algorithmen/pattern/bmen.htm

对我来说没有意义......有人可以为此任务编写尽可能简单的伪代码吗?或者以某种方式解释?

1 个答案:

答案 0 :(得分:11)

首先,我将使用p[i]表示模式中的字符,m模式长度,$模式中的最后一个字符,即$ = p[m-1]

良好的后缀启发式案例1有两种情况。

情况1

考虑以下示例,

    leading TEXT cXXXbXXXcXXXcXXX rest of the TEXT
         cXXXbXXXcXXXcXXX
                     ^ 
                     | mismatch here

因此模式XXX中的子字符串cXXXbXXXcXXXcXXX是良好的后缀。不匹配发生在角色c。所以在不匹配之后,我们应该向右移动4还是8?

如果我们按照以下方式移动4,则会再次出现同样的错误(b错配c),

    leading TEXT cXXXbXXXcXXXcXXX rest of the TEXT
             cXXXbXXXcXXXcXXX
                     ^ 
                     | mismatch occurs here again

因此,在这种情况下,我们实际上可以将8个字符向右移动。

情况2

让我们看看另一个例子

    leading TEXT cXXXcXXXbXXXcXXX rest of the TEXT
            cXXXXcXXXbXXXcXXX
                         ^ 
                         | mismatch happens here

我们可以在这里换4或8或更多吗?显然,如果我们移动8或更多,我们将错过将模式与文本匹配的机会。所以在这种情况下我们只能向右移4个字符。

那么这两种情况有什么区别?

不同之处在于,在第一种情况下,不匹配的字符c加上良好的后缀XXX,即cXXX,与下一个字符相同(从右侧算起)匹配XXX加上之前的字符。在第二种情况下,cXXX与下一场比赛(从右边开始计数)加上该比赛前的角色不同。

因此,对于任何给定的GOOD SUFFIX(我们称之为XXX),我们需要弄清楚这两种情况的转变,(1)角色(我们称之为c)之前GOOD SUFFIX加上GOOD SUFFIX,在模式中也匹配下一个(从右边算起)匹配好的后缀+前面的字符,(2)字符加上GOOD SUFFIX不匹配

对于情境(1),例如,如果您有一个模式0XXXcXXXcXXXcXXXcXXXcXXX,如果在c的第一次测试失败后,您可以向右移动20个字符,并对齐{{ 1}}与已测试的文本。

这是计算数字20的方式,

0XXX

不匹配发生的位置是20,移位的子串 1 2 012345678901234567890123 0XXXcXXXcXXXcXXXcXXXcXXX ^ ^ 将从20到23的位置。0XXX从位置0开始,所以20 - 0 = 20。

对于情境(2),如本示例0XXX,如果在0XXXaXXXbXXXcXXX的第一次测试失败后,您只能向右移动4个字符,并对齐c经过测试的文本。

这是数字bXXX的计算方式,

4

发生不匹配的位置是12,取出该位置的下一个子串是 0123456789012345 0XXXaXXXbXXXcXXX ,它从位置8开始,12 - 8 = 4.如果我们设置bXXX,{{1然后,移位为j = 12,代码中为i = 8

<强>边界

要考虑所有好的后缀,我们首先需要了解什么是所谓的j - i。 边框是子字符串,它是字符串的s[j] = j - i;前缀和border后缀。例如,对于字符串properproper是边框,XXXcXXX也是边框,X。但是XX不是。我们需要确定模式后缀的最宽边界的起点,此信息保存在数组XXX中。

如何确定XXXc

假设我们知道f[i]以及f[i]的所有其他f[i] = j,这意味着从位置f[k]开始的后缀最宽的边界从{{1}开始}}。我们希望根据i < k < m找到i

例如,对于模式j,位置f[i-1],后缀为f[i],最宽的边框为aabbccaacci=4 ),ccaacc。现在,我们希望根据ccp[8]p[9]的信息来了解j = f[i=4] = 8,对于f[i-1] = f[3],后缀现在为f[4]。在f[5]的位置,f[3]!= bccaaccj-1=7。所以a不是边界。

我们知道p[4-1]的宽度&gt; = 1的任何边框都必须以b加上从positin bcc开始的后缀边框开始,即bccaacc在这个例子中。 bj = 8位置cc最宽,cc。因此,我们将cj = f[8]进行比较,继续进行搜索。他们不再平等。现在后缀为9,并且在p[4-1]位置只有零长度边框。所以现在p[j-1],它是p[9] = c。因此,我们继续我们的搜索,将10j = f[9]进行比较,它们不相等,这是字符串的结尾。然后10只有零长度边界,使其等于10。

以更一般的方式描述流程

因此,p[4-1]意味着这样的事情,

p[j-1]

如果位置f[3]的字符f[i] = j与位置 Position: 012345 i-1 i j - 1 j m pattern: abcdef ... @ x ... ? x ... $ 的字符@相同,我们知道 i - 1?。从位置j - 1开始,边框为后缀f[i - 1] = j - 1;

但如果位置--i; --j; f[i] = j;的字符@x ... $与位置j-1的字符@不同, 我们必须继续向右搜索。我们知道两个事实:(1)我们现在知道边界宽度必须小于从位置i - 1开始的宽度,即小于?。其次,边界必须以j - 1开头,以字符j结尾,否则可能为空。

基于这两个事实,我们继续在子字符串x...$内(从位置j到m)搜索以@...开头的边界。然后下一个边框应该是$,等于x ... $,即x。然后我们将字符jf[j];之前的字符进行比较,该字符位于j = f[j];。如果它们相等,我们发现边界,如果没有,则继续该过程直到j> 1。米此过程由以下代码

显示
@

现在查看条件x p [j-1] j-1 p [i] while (j<=m && p[i-1]!=p[j-1]) { j=f[j]; } i--; j--; f[i]=j; p [j] p[i -1] != p [i -1]!= { {1}},所以我们从, this is what we talked about in situation (2),转移到matches,即, but

现在唯一没有解释的是测试p[j-1],当较短的后缀具有相同的边框时会发生。例如,您有一个模式

i

当您计算js[j] = j - i;时,您将设置if (s[j] == 0)。但是当您为 012345678 addbddcdd 计算f[i - 1]时,如果您没有测试i = 4,则会再次设置s[7]。这意味着如果您在f[i-1]位置不匹配,则将i = 1向右移动(将s[7]if (s[j] == 0)占据的位置对齐)而不是6(不移位)直到3bdd所占据的位置。

代码评论

cdd