您希望在后缀数组中对所有后缀进行排序的主要原因有两个。
首先,如果 S 和 T 是字符串,我们知道以下内容:
T 是 S 的子串当且仅当它是 S 的后缀的前缀。
例如,如果 S 是“avoidance”而 T 是“ida”,那么 T 是 S 的子字符串,因为它是后缀“idance”的前缀。因此,需要快速查询 S 的子串的应用程序可以改写为搜索 S 的后缀前缀。
鉴于此,如果您对搜索 S 的后缀前缀感兴趣,将这些后缀存储在允许快速搜索的数据结构中是有意义的。如果我们将后缀放在一个数组中,保持它们的排序,那么您可以查找各种前缀必须有效的位置。因此,将后缀数组作为按排序顺序存储的 S 的所有后缀的数组,可以快速搜索后缀的前缀,从而搜索 S 的子字符串。
关于 LCP 数组的第二个问题 - 如果未对后缀进行排序,您能否计算它们?如果这样做,您会失去什么? - 你绝对可以为任何数组计算它们,甚至是未排序的后缀数组,所以没有根本原因你不能这样做。但是,已排序后缀数组的 LCP 数组具有许多不错的属性,而未排序后缀数组的 LCP 数组则没有。例如,后缀数组中的 LCP 数组可用于确定对应后缀树中内部节点的深度,或计算最长公共扩展等。
排序后缀数组和 LCP 的一个非常重要的属性是,如果您计算所有字符串的成对 LCP 信息,您可以通过对 LCP 数组执行范围最小查询来计算任意字符串对的 LCP。这样做的原因是,如果对后缀进行排序,则会保留相邻字符串之间的最大重叠量。这在数组未排序的情况下不起作用(我将在最后再次提到这一点。)
要具体查看问题的出处,让我们以最长重复子串问题为例。使用后缀数组的正常线性时间算法如下:
- 为字符串 T 构造一个后缀数组。
- 为广义后缀数组构造 LCP 数组。
- 遍历后缀数组,找到LCP值最大的字符串。
重要的是要思考为什么最后一步有效。考虑任何重复两次的子字符串,将其称为 S。因为任何子字符串都是后缀的前缀,这意味着字符串 Sα 和 Sβ 必须是字符串 T 的后缀。如果按排序顺序存储后缀数组,则所有字符串以前缀 S 开头的将连续出现在后缀数组中(你明白为什么吗?)。因此,如果 S 是最长的重复子串,那么以 S 开头的第一个后缀有一个 LCP,其下一个长度为 |S|。
现在,考虑一下如果你这样做而不对数组进行排序会发生什么。在这种情况下,如果 S 是最长的重复子串,则字符串 Sα 和 Sβ 仍然是字符串 T 的后缀。但是,它们在后缀数组中不一定是连续的,因此不一定是线性的-找到它们的时间算法。例如,考虑字符串
abracadabra
未排序的后缀数组是
abracadabra$
bracadabra$
racadabra$
acadabra$
cadabra$
adabra$
dabra$
abra$
bra$
ra$
a$
$
用LCP信息标注后,我们得到
0 abracadabra$
0 bracadabra$
0 racadabra$
0 acadabra$
0 cadabra$
0 adabra$
0 dabra$
0 abra$
0 bra$
0 ra$
0 a$
$
所以你可以看到这个算法不会找到“abra”,因为它们不是连续的。您仍然可以通过尝试所有对来想象它是“abra”,但这对于大字符串来说效率不高。
我之前提到过,排序后缀数组中相邻字符串对的 LCP 信息可用于计算排序后缀数组中任意字符串对的 LCP 信息。如果字符串未排序,则不是这样。在上面,您可以看到字符串的相邻成对 LCP 均为 0,即使某些字符串确实具有非零公共前缀。
希望这会有所帮助!