杜瓦尔(Duval)的算法如何处理奇数长度的字符串?

时间:2019-04-11 23:52:37

标签: string algorithm rotation lexicographic

发现Lexicographically minimal string rotation是一个众所周知的问题,让·皮埃尔·杜瓦尔(Jean Pierre Duval)于1983年提出了一个linear time algorithm。博客This可能是唯一公开谈论的资源。详细的算法。但是,Duval的算法基于成对比较(“ duels”)的概念,并且博客方便地使用偶数长度的字符串作为示例。

该算法如何处理奇数长度的字符串,而最后一个字符将没有与之竞争的对决?

2 个答案:

答案 0 :(得分:1)

一个人物可以赢得“ bye”,而无需参加“决斗”就可以获胜。算法的正确性不依赖于您执行的特定对决。给定 any 两个不同的索引 i j ,您始终可以得出结论,其中之一是字典最小的起始索引旋转(除非都是是词典最小旋转的相同的起始索引,在这种情况下,您拒绝哪个旋转都没有关系)。按特定顺序执行决斗的原因是性能:通过确保一半的决斗只需要比较一个字符,其余的一半只需要比较两个字符来获得渐近线性时间,依此类推,直到最后一次决斗只需要比较字符串长度的一半即可。但是,这里和那里的单个奇数字符并没有改变渐近复杂性,它只是使数学(和实现)更加复杂。长度为2 n +1的字符串仍然需要比长度2 n +1 更少的“决斗”

答案 1 :(得分:0)

此处操作:我接受了ruakh的回答,因为它与我的问题有关,但我想为可能会偶然发现这篇文章的其他人提供自己的解释,以了解Duval的算法。

问题:

  

从字法上讲,最小圆形子串是查找的问题   具有最低词典顺序的字符串的旋转   所有这些旋转。例如,字典上最小的   旋转“ bbaaccaadd”将是“ aaccaaddbb”。

解决方案:

Jean Pierre Duval(1983)提出了一种O(n)时间算法。

给定两个索引ij,Duval的算法比较了从j - ii开始的长度为j的字符串段(称为”决斗” )。如果index + j - i大于字符串的长度,则该段通过环绕而形成。

例如,考虑s =“ baabbaba”,i = 5,j =7。由于j-i = 2,所以从i = 5开始的第一段是“ ab”。从j = 7开始的第二个段是通过环绕而构建的,并且也是“ ab”。 如果字符串在字典上是相等的,例如上面的示例,我们选择从i开始的那一个作为获胜者,即i = 5。

重复上述过程,直到我们有一个获胜者。如果输入字符串的长度为奇数,则在第一个迭代中无需比较就可以赢得最后一个字符。

时间复杂度:

第一次迭代比较长度为1的n个字符串(n / 2个比较),第二次迭代可以比较长度为2的n / 2个字符串(n / 2个比较),依此类推,直到第i次迭代比较2个长度为n / 2的字符串(n / 2个比较)。由于每次获胜者的数量减半,因此递归树的高度为log(n),因此我们得到了O(n log(n))算法。对于小n,大约为O(n)。

空间复杂度也是O(n),因为在第一次迭代中,我们必须存储n / 2个获胜者,第二次迭代中要存储n / 4个获胜者,依此类推。 (维基百科声称此算法使用恒定空间,我不知道如何使用。)

这是一个Scala实现;随时转换为您喜欢的编程语言。

def lexicographicallyMinRotation(s: String): String = {
 @tailrec
 def duel(winners: Seq[Int]): String = {
   if (winners.size == 1) s"${s.slice(winners.head, s.length)}${s.take(winners.head)}"
   else {
     val newWinners: Seq[Int] = winners
       .sliding(2, 2)
       .map {
         case Seq(x, y) =>
           val range = y - x
           Seq(x, y)
             .map { i =>
               val segment = if (s.isDefinedAt(i + range - 1)) s.slice(i, i + range)
               else s"${s.slice(i, s.length)}${s.take(s.length - i)}"
               (i, segment)
             }
             .reduce((a, b) => if (a._2 <= b._2) a else b)
             ._1
         case xs => xs.head
       }
       .toSeq
     duel(newWinners)
   }
 }

 duel(s.indices)
}