算法导论32 Problems 答案
32-1
a
首先使用KMP算法计算出$\pi$数组,分两种情况进行考虑:
如果$i\bmod (i-\pi[i])=0$,那么有$\rho(P[:i])=\dfrac{i}{i-\pi[i]}$,这意味$P[i-\pi[i]+1:i]=P[1:\pi[i]]$,那也就是说,$P[\pi[i]+1:2\pi[i]]=P[1,\pi[i]]$,类似的,有$P[i-2\pi [i]+1:i]=P[i-\pi [i]+1:i]$,最终多次迭代下去,可以发现这是一个周期为$\dfrac{i}{i-\pi[i]}$的字符串,此外,由于$\forall k\in \pi^{\ast}[i]$,都有$k\le \pi[i]$,因此$i-\pi[i]$是$i$满足条件的最小因子,它将使值$\dfrac{i}{i-k}$达到最大,因此原结论成立。
如果$i\bmod (i-\pi[i])\neq 0$,那么$\rho(P[:i])=1$。因为,对于一个非周期字符串$s$(如果是周期字符串,那么可以归约到最小非周期字符串),如果存在一个正整数$r$满足$s^r=P[i]$,那么有$r\mid i$。然而按照上面的方式依次取出$r$个字母,它们不能取完整,因此这时$P[:i]$只能由自身拼接$1$次而成,原结论成立。
算法COMPUTE-PREFIX-RHO给出了具体过程,可见其时间复杂度为$O(n)$。
1 | COMPUTE-PREFIX-RHO(P, m) |
b
待研究,相关链接:
c
同题目32-1-b。
32-2
a
假设目前需要对比$P$中非空后缀$i$和$j$,令$P[i]$表示$P$的第$i$个元字符。如下考虑多种情况:
如果$P[i]\neq P[j]$,那么第$i$个非空后缀和第$j$个非空后缀的字典序由$P[i]$和$P[j]$决定,此时和$P[i],P[j]$后面的字符没有任何关系,因此原结论成立。
如果$P[i]=P[j]$,考虑$i,j$都在$P$中$P_1$所占有的下标。可见,由于$P_1[i:]\varnothing$和$P_1[j:]\varnothing$的长度不同,并且最后一个字符$\varnothing$都小于已经出现的字符,因此它们的字典序已经确定。此时再在独立的字符串$P_1[i:]\varnothing,P_1[j:]\varnothing$后面再添加字符也不会改变$P_1[i:]\varnothing$和$P_1[j:]$的字典序。因此原结论成立。接下来考虑$i,j$都在$P$中$P_2$所占有的下标,明显原结论成立,因为这些后缀恰好是$P_2$的后缀。
如果$P[i]=P[j]$,考虑$i,j$在$P$中,其中一个在$P_1$所占有的下标,另一个在$P_2$所占有的下标。不失一般性,假设$i$属于前者,$j$属于后者,那么不难得到$i<j$。由于此时$P[i]=P[j]$,因此我们继续向下找下一个下标,直到满足这两个元字符不相同,即最小的正整数$s$,使得$P[i+s]\neq P[j+s]$。可以发现$i+s$仍会在$P_1$占有的下标中。按照步骤A的作用,$P_1$必定会包含一个出现$\varnothing$的元字符,因此第$i$个非空后缀不晚于这个元字符结束。由于$n’\not\equiv n’’\pmod 3$,因此$i$个非空后缀和第$j$个非空后缀必定是以不同数量的$\varnothing$元字符作为结尾,此外由于$\varnothing$的字典序最小,再在它们的后面任意添加字符也不会改变这两个非空后缀的字典序,因此原结论成立。
因此,$P$的非空后缀的排名和$P$的后缀排名是相同的。由于早在$\varnothing$的时候就已经确定了排名,去除$\varnothing$后面的字符并不影响后缀的排名,因此$P$中的后缀排名和$T$的采样后缀的相对排名是一致的,因此原结论成立。
b
由于每个元字符都恰好是一个三元组,因此我们可以使用基数排序完成,只需要进行三趟。由于字符的大小有限,因此每趟基数排序的内部使用计数排序,最终可以在$\Theta(n)$的时间内完成这个排序过程。需要注意的是,由于前面的字符占主导地位,因此基数排序过程是对这些三元组中的每个字符从后往前进行。如下算法SA-SORT-CHARACTERS给出了这个过程,可见其时间复杂度为$\Theta(n)$。
1 | // 假设字符出P中的每个字符用三元组(s[1], s[2], s[3])表示,分别表示元字符中的第一、第二和第三个字符。 |
c
假设后缀$P[i:]$是非采样后缀,那么有$ri=\square,r{i+1}$是一个非负整数。如果$ri\neq \square$,那么说明$r_i$是进行递归后,对应采样后缀的排名,因此这些$r_i$是非负整数的情况下是唯一的,因此这使得二元组$(T[i],r{i+1})$是唯一的。
由于原字符$T[i]$的范围非常小,因此我们同样可以使用题目32-2-b的思路,使用基数排序(每趟内部使用计数排序)对这些二元组进行排序。如下算法SA-SORT-CHARACTERS给出了这个过程,可见其时间复杂度为$\Theta(n)$。
1 | SA-SORT-NONSAMPLE-SUFFIXES(T, r, n) |
d
假设字符串$T$后面已经拼接了两个$\varnothing$,对应到$r$数组中其值为$0$,也就是说,字符串$T$现在的长度为$n+2$。
其基本思想是,对于任意一对下标$i,j(i\not\equiv j\pmod 3,1\le i,j\le n)$,只要最多比较三次就能比较出字典序。也就是说,只要比对$(P[i],P[j]),(P[i+1],P[j+1]),(P[i+2],P[j+2])$即可。可以发现,必定存在最小的$s(0\le s\le 2)$,使得$r{i+s}\neq\square\land r{j+s}\neq\square$。
具体过程由SA-SORT-MERGE给出,它将原有的$r$数组以及题目32-2-d所排好序的二元组进行归并,返回最终得到的$SA$数组。最终这个过程在$\Theta(n)$的时间内完成。
1 | // 字符串T的长度为n+2,包括后面已经拼接了两个∅,r[n + 1] = r[n + 2] = 0。 |
e
综上所述:
步骤1进行的是第一步分治。子步骤A和B花费了$\Theta(n)$的时间来构造字符串$P,P_1,P_2$。子步骤C使用了题目32-2-b的算法,对字符串$P$中的字符进行排序,并通过子步骤D进行产生$P$中每个字符的排名,存在数组$P’$中,这个过程使用了基数排序和计数排序的方法,总共花费了$\Theta(n)$的时间。子步骤E递归构造$P’$的$SA$数组,由于其长度为$2n/3$,因此需要花费$T(2n/3)$的时间。子步骤F则使用$\Theta(n)$的时间将$P’$的$SA$数组映射回采样后缀中。
步骤2进行的是第二步分治。子步骤G使用$\Theta(n)$的时间求出$r$数组。子步骤H使用了题目32-2-c的算法,对$(T[i],r_{i+1})$二元组进行排序,由于同样使用了基数排序和计数排序的方法,因此总共花费了$\Theta(n)$的时间。
步骤3进行的是归并。它将两个分支步骤的结果进行归并。使用题目32-2-d的算法在$\Theta(n)$时间内求出最终的$SA$数组。
因此,除了步骤1的子步骤E需要求解规模为$2n/3$的子问题,其余步骤都需要花费$\Theta(n)$的时间进行处理,因此有$T(n)=T(2n/3)+\Theta(n)$。按照主定理,有$T(n)=\Theta(n)$,因此这是一个线性时间求解$SA$数组的算法。
32-3
a
需要注意的是,拼接的字符$\mathtt{$}$的字典序是所有字符中最小的。可以假定,在为字符串构造SA数组时,它后面都有一个终结符$\mathtt{$}$。因此,在对所有后缀排好序后,可以发现每个后缀的排名都是唯一的,此时在这些后缀后面如何加字符,都不会影响它们原本的排名。因此,BWT只需要挪用$SA$数组的结果即可,不过需要先在$SA$数组的最前面再插入一个值$|T|+1$,那么这时的结果才是BWT数组产生的结果。最终,字符串$t[i]=T[(BWT [i]-1-1)\bmod (|T|+1) + 1]$才是BWT的结果。更具体的过程由BWT-KNOWN-SA给出。
1 | BWT-KNOWN-SA(T, SA, n) |
b
基于插入排序的思想就可以计算出每个下标的排名,如下给出BWT-COMPUTE-RANK将给出计算$rank$数组的算法。如果字母表中一共有$k$个字符,并且其范围是从$1$到$k$,那么BWT-COMPUTE-RANK计算$rank$数组的时间复杂度为$\Theta(n+k)$。如果$k=O(n)$或者是一个常数,那么就有$\Theta(n+k)=\Theta(n)$。
1 | BWT-COMPUTE-RANK(T, n, k) |
c
只需要按照题目的含义进行模拟即可,先从$\mathtt{$}$开始填充,因为它必定处于最后一位。整个过程由BWT-INV给出,可见其由于只有一个for循环进行常数操作,因此其时间复杂度为$\Theta O(n)$。
1 | BWT-INV-RANK(T, rank, n) |