“循环移位等价类明确哪些线性记录只相差切口;最小循环表示可在线性时间选取一个规范代表,并在周期串上报告全部并列起点。这种选择会有意忘记原切口,不能代替无终止符 BWT 为恢复原始线性文本保存的…”
两份循环日志分别为 bbaabbaabbaa 和 aabbaabbaabb。按位置比较,它们不同;允许更换起点后,它们却是同一圈。可以把每份日志变成字典序最小的旋转,再比较规范结果。
最直接的方法要比较全部
形式陈述
先明确返回什么
对非空输入
内层选内容最小的旋转,外层在内容相等时选最小的原始下标。本页主算法返回
空输入约定返回起点零与空内容。这是方便调用的接口约定,不把零解释为空词上的模运算。算法首先处理它,后续的取模都只用于
这里固定字符的全序,保留读入方向和总长度。若只想识别循环模式而忽略重复了几圈,要先取本原根;若还允许反向读取,则需要另外比较反串的规范形式。接口不同,不能在实现里静默丢掉这些信息。
双候选扫描
维护候选
def minimal_rotation_index(s):
n = len(s)
if n == 0:
return 0
i, j, k = 0, 1, 0
while i < n and j < n and k < n:
x = s[(i + k) % n]
y = s[(j + k) % n]
if x == y:
k += 1
continue
if x > y:
i += k + 1
if i == j:
i += 1
else:
j += k + 1
if i == j:
j += 1
k = 0
return min(i, j)
代码直接通过取模访问原串,没有创建
直觉
第一次失配为什么能淘汰一段
设两个候选起点为
于是起点
对任意
这个证明没有要求胜者
停下时为什么已经有全局答案
循环保持的范围不变量是:小于
若某一候选跳到
不妨此时
整个过程只严格淘汰内容更大的旋转,故不会误删一个下标更小但内容相等的最优起点。返回
例子与边界
复算一个带周期平局的例子
对 bbaabbaabbaa,从 b,下一位是 b>a,于是跳过起点零、一,令 a<b,故
现在比较起点二和三:第一位同为 a,第二位 a<b,起点三、四被淘汰,b 开头,又输给起点二,
起点二和六开始的整圈都为 aabbaabbaabb,连续比较十二位全部相等,
单字符输入从一开始就有 aaaa 则在起点零、一之间匹配满四位,返回零。二者都不需要特殊的内部循环补丁,只需要入口处避免空串取模。
推论与应用
把比较次数记到淘汰的区间上
一轮失配前如果有
返回起点不需要复制字符串;真正输出长
全部最优起点与本原代表
设输入的本原根长度为
由于
例子中 aabb,它是本原根循环类的唯一Lyndon 代表;规范整串则是
单元终结任务:给循环日志交付一份可核验结果
给定
- 写出前缀函数末值、最短周期、本原根和指数
- 给出 CFL 分解的全部半开区间,并解释为何每块合法且非增
- 手算 Duval 的三个输出批次,说明第二轮为何留下
aa - 求最小旋转、最小起点和全部并列起点,给出每次失配淘汰的范围
- 比较
aabbaabb与 :若保留整圈长度,它们是否相同;若只比较本原循环模式,结论是否改变
核对结果:末值八,最短周期四,根 bbaa,指数三;CFL 区间为 aabbaabbaabb,最小起点二,全部起点 aabbaabb 不与长度十二的 aabb,因此在“忽略圈数”的另一个接口下相同。
最后加一个拒绝测试:长度六的 abaaba 虽有周期三和五,不能合并成周期一,因为
配套的可执行核验脚本枚举短词,将线性算法与独立暴力定义对照,同时核验周期、本原根、CFL 唯一切分、全部旋转起点和空串接口。小规模穷举能抓住索引错误,正确性的普遍保证仍来自上面的不变量与淘汰证明。
参考资料
- [1] K. S. Booth, “Lexicographically Least Circular Substrings”, Information Processing Letters 10(4–5), 1980, pp. 240–242,DOI:修改失败函数的经典线性算法。
- [2] Yossi Shiloach, “Fast Canonization of Circular Strings”, Journal of Algorithms 2(2), 1981, pp. 107–121:全部最小起点、周期间隔与常数辅助存储;该文的更优比较常数不是本文双候选实现的保证。
- [3] Jean-Pierre Duval, “Factorizing Words over an Ordered Alphabet”, Journal of Algorithms 4(4), 1983, pp. 363–381:Lyndon 分解及最小循环移位应用。