如何有效地实现任意序列的按位旋转?

时间:2019-03-28 11:59:51

标签: c++ algorithm bit-manipulation

“由索引置换p(i)=(i + k)mod n定义的n个元素的置换p称为k- 旋转”。 -Stepanov & McJones

std::rotate由于Sean Parent而成为一种众所周知的算法,但是如何有效地对任意位序列实现它呢? 高效是指至少将两件事减到最少:i)写数和ii)最坏情况下的空间复杂度。

也就是说,输入应类似于std::rotate,但按位特定,我想是这样的:

  1. 一个指向位序列开始的存储器的指针。
  2. 三个位索引:firstmiddlelast

指针的类型可以是任何无符号整数,并且可能越大越好。 (Boost.Dynamic Bitset称之为“块”。)

重要的是要注意,索引可能都从一个块的开头偏移了不同的量。

根据Stepanov和McJones的说法,可以在n + gcd(n,k)分配中实现对随机访问数据的轮换。反转每个子范围然后反转整个范围的算法需要分配3n个分配。 (但是,我同意下面的评论,即实际上是2n个分配。)由于可以随机访问数组中的位,因此我认为适用相同的最佳界限。由于不同的子范围块偏移量,每个分配通常需要两次读取,但是与写入相比,我较少关注读取。

在野外开源中是否已经存在这种算法的有效或最佳实现? 如果没有,怎么办?

我浏览了《黑客的喜悦》和Knuth的第4A卷,但找不到用于它的算法。

1 个答案:

答案 0 :(得分:2)

例如,使用vector<uint32_t>可以很容易且合理有效地自己完成一次旋转的小数部分(shift_amount%32),然后调用std::rotate来进行休息。小数部分很容易,并且只能在相邻元素上操作(末端除外),因此您在工作时只需要记住一个部分元素即可。

如果您想自己完成整个操作,则可以通过反转整个矢量的顺序,然后反转前部和后部的顺序来进行旋转。有效执行此操作的技巧是,当您反转整个向量时,您实际上并没有对每个元素进行位反转-您只是认为它们的顺序相反。前后部分的反转比较棘手,需要在工作时记住4个部分元素。

就写入内存或缓存而言,以上两种方法均进行 2N 次写入。您在问题中所指的最佳轮换采用 N ,但是如果将其扩展为使用小数词轮换,则每次写入跨越两个单词,然后采用 2N 写道。它没有优势,我认为结果会很复杂。

这就是说...我敢肯定,通过一次执行 m 个字,您可以更接近 N 写入并以固定的寄存器存储量进行写操作,但这就是不过,可以使用大量代码进行简单的轮换,这样,您的时间(或至少是我的时间 :)最好花在其他地方。