从后缀数组获取LCP的代码如何工作?

时间:2014-10-17 15:41:11

标签: c++ algorithm suffix-array

有人可以解释这个从后缀数组构建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";


}

1 个答案:

答案 0 :(得分:7)

首先,重要的是要认识到算法按原始顺序处理后缀,即它们在输入字符串中出现的顺序。不是字典顺序。

因此,如果输入字符串为abxabc,则首先考虑abxabc,然后考虑bxabc,然后考虑xabc,依此类推。

对于它按此顺序考虑的每个后缀,它确定后缀的位置,即它的词典前任(*)(所以这里,只有在这里,它使用词典顺序的概念) 。对于第一个后缀abxabc,词典编辑的前任,即在后缀的词典排序中直接出现在后面的后缀是abc。它通过数组C中的O(1)查找来确定这一点,该查询是专门为此目的而准备的。

内部循环逐个比较abxabcabc的字符,并确定这两个后缀共有前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 前身,即后缀为它立即留在后缀数组中。