有人可以解释这个从后缀数组构建LCP的代码是如何工作的吗? suffixArr[]
是一个数组,suffixArr[i]
保存字符串 i 后缀的字符串中的索引值。
void LCPconstruct()
{
int i,C[1001],l;
C[suffixArr[0]] = n;
for(i=1;i<n;i++)
C[suffixArr[i]] = suffixArr[i-1];
l = 0;
for(i=0;i<n;i++)
{
if(C[i]==n)
LCPadj[i] = 0;
else
{
while(i+l<n && C[i]+l<n && s[i+l] == s[C[i]+l])
l++;
LCPadj[i] = l;
l = max(l-1,0);
}
}
for(i=0;i<n;i++)
cout<<LCPadj[suffixArr[i]]<<"\n";
}
答案 0 :(得分:7)
首先,重要的是要认识到算法按原始顺序处理后缀,即它们在输入字符串中出现的顺序。不是字典顺序。
因此,如果输入字符串为abxabc
,则首先考虑abxabc
,然后考虑bxabc
,然后考虑xabc
,依此类推。
对于它按此顺序考虑的每个后缀,它确定后缀的位置,即它的词典前任(*)(所以这里,只有在这里,它使用词典顺序的概念) 。对于第一个后缀abxabc
,词典编辑的前任,即在后缀的词典排序中直接出现在后面的后缀是abc
。它通过数组C
中的O(1)查找来确定这一点,该查询是专门为此目的而准备的。
内部循环逐个比较abxabc
和abc
的字符,并确定这两个后缀共有前2个字符。这是代码中的变量l
,这意味着LCP中后缀abxabc
的条目必须为2,因此我们设置LCPadj[i] = l
。请注意,此处i
指的是输入字符串中后缀的位置,而不是后缀数组中的位置。所以LCPadj
不是LCP数组(尚未)。这是一个辅助数据结构。
然后进入下一个字符串,即bxabc
。同样,它使用C
来查找bc
是该词典的前导词,然后确定两者共享多少个前缀字符。这就是诀窍:可以肯定的是,它必须至少与上一步(即2)中的数量相同,减去1.为什么?因为我们当前考虑的字符串bxabc
当然是先前考虑的字符串的后缀(abxabc
),因此该字符串的词典编辑前导(abc
)也必须具有后缀这是1个字符短(bc
),并且后缀也必须在后缀数组中的某个位置,并且它必须与当前考虑的字符串共享其前缀,减去第一个字符。此外,不能有任何其他后缀更短和按字典顺序更接近当前考虑的字符串。如果你考虑字典排序的工作原理,后者是合乎逻辑的,但也有正式的证明(例如Kärkkäinen的讲座中的引理5.10 here)
这就描述了这里的主要原则。关于完全理解每个变量的作用,您需要注意一些关于代码的注意事项:
C
是一个辅助数组(长度为n
整数),它为输入字符串中的每个后缀存储另一个后缀的位置,该后缀是其直接的词典前导。这个数组不是从左到右构造的,而是(明智地)从左到右遍历后缀数组,因为这样可以很容易地确定任何字符串的直接词典上的前导:从位置开始的后缀的直接词典编辑前身suffixArr[i]
当然必须位于suffixArr[i-1]
位置。在代码中确认这是C
的定义方式。LCPadj
按照它们在输入字符串中出现的顺序存储后缀的LCP值,而不是它们在后缀数组中出现的顺序。这就是为什么在输出时,LCPadj
不是从左到右打印,而是从左到右打印后缀数组,然后按顺序打印LCPadj[i]
。确认是这种情况。我希望这会有所帮助。如果没有,请告诉我。
(*) 词典编辑的前身我的意思是后缀词典顺序列表中后缀的 immediate 前身,即后缀为它立即留在后缀数组中。