我理解不良角色的启发式工作原理。当您找到不匹配的字母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的对我来说没有意义......有人可以为此任务编写尽可能简单的伪代码吗?或者以某种方式解释?
答案 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
后缀。例如,对于字符串proper
,proper
是边框,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]
,最宽的边框为aabbccaacc
(i=4
),ccaacc
。现在,我们希望根据cc
,p[8]p[9]
的信息来了解j = f[i=4] = 8
,对于f[i-1] = f[3]
,后缀现在为f[4]
。在f[5]
的位置,f[3]
!= bccaacc
即j-1=7
。所以a
不是边界。
我们知道p[4-1]
的宽度&gt; = 1的任何边框都必须以b
加上从positin bcc
开始的后缀边框开始,即bccaacc
在这个例子中。 b
在j = 8
位置cc
最宽,cc
。因此,我们将c
与j = f[8]
进行比较,继续进行搜索。他们不再平等。现在后缀为9
,并且在p[4-1]
位置只有零长度边框。所以现在p[j-1]
,它是p[9] = c
。因此,我们继续我们的搜索,将10
与j = 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
。然后我们将字符j
与f[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
当您计算j
和s[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
(不移位)直到3
到bdd
所占据的位置。
代码评论
cdd