std::rotate()将完全满足您的需求:
auto b = std::begin(A);
std::rotate( b, b + 2, std::end(A) );
Run Code Online (Sandbox Code Playgroud)
矢量旋转似乎是神秘算法的沃土。其中大部分都可以在野外找到,但有变化;所有高效的都需要一些思考来了解它们的功能。
如果你只向左旋转一个元素,你可以非常有效地做到这一点:
template<typename Iter>
void rotate_one(Iter first, Iter last) {
using Value = typename Iter::value_type;
if (first != last) {
Value temp = std::move(*first);
for (Iter next = std::next(first);
next != last;
first = next, next = std::next(next))
*first = std::move(*next);
*first = std::move(temp);
}
}
Run Code Online (Sandbox Code Playgroud)
您可以使用它delta通过执行?次数(更准确地说,? % N)来按位置旋转,但这需要时间 O(N?),对于任意 ?,这实际上是 O(N²)。
虽然这个解决方案通常如上所示,但也可以在没有临时值对象的情况下实现它,使用交换而不是移动:
template<typename Iter>
void rotate_one(Iter first, Iter last) {
if (first != last) {
for (Iter next = std::next(first); next != last; ++first, ++next)
std::iterswap(first, next);
}
}
Run Code Online (Sandbox Code Playgroud)
交换通常比移动要多一些工作,但有可能有一个高效的特定于容器的交换实现。无论如何,这个版本将有助于理解以后的实现。
一个众所周知且经常被引用的 O(N) 解决方案是做三个逆向:
template<typename Iter>
void rotate_one(Iter first, Iter newfirst, Iter last) {
std::reverse(first, newfirst);
std::reverse(newfirst, last);
std::reverse(first, last);
}
Run Code Online (Sandbox Code Playgroud)
在我看来,这真的很优雅。您可以通过在纸上进行尝试来了解它是如何工作的:
a b ... c d w x ... y z
d c ... b a w x ... y z first rotate
d c ... b a z y ... x w second rotate
w x ... y z a b ... c d third rotate
Run Code Online (Sandbox Code Playgroud)
这是众所周知的“颠倒句子中单词的顺序”解决方案的一个特例,它包括首先颠倒每个单词的字母,然后颠倒整个字符串。
但是,它存在一些问题。首先,当一次移动就足够时,它(几乎)每个元素移动两次。其次,std::reverse需要一个双向迭代器。这没有任何问题,但算法与任何前向迭代器一起工作会更好。
另一个简单的解决方案是注意,如果您使用第一个算法,但使用增量 Δ 而不是增量,并在迭代器到达末尾时将迭代器包装回开头,那么如果 Δ 和 N 互质,您将正确旋转向量. 但是,如果它们不是素数,则您只会旋转一些元素;指数为 0 模 gcd(N, Δ) 的那些。要旋转整个向量,您需要对向量中的每个前 gcd(N, Δ) 元素执行 gcd(N, Δ) 次。
这是一个包含 12 个元素且 Δ 为 3 的插图:
a b c d e f g h i j k l
\ / / /
\/ / /
/ \ / /
/ /\ /
/ / \/
/ / / \
d b c g e f j h i a k l first loop
\ / / /
\/ / /
/ \ / /
/ /\ /
/ / \/
/ / / \
d e c g h f j k i a b l second loop
\ / / /
\/ / /
/ \ / /
/ /\ /
/ / \/
/ / / \
d e f g h i j k l a b c third loop
Run Code Online (Sandbox Code Playgroud)
使用随机访问迭代器更容易(这是一个缺陷);这是一个示例实现。(该变量count计算已移动元素的数量;每个元素移动一次,因此当count达到 0 时,旋转完成。这避免了必须计算 GCD 以了解运行外循环的次数。)
template<typename Container>
void rotate(Container& A, typename Container::size_type delta) {
using Value = typename Container::value_type;
using Index = typename Container::size_type;
Index n = A.size();
delta %= n;
for (Index loop = 0, count = n;
count;
++loop, --count) {
Index dst = loop;
Value tmp = std::move(A[dst]);
for (Index src = loop + delta;
src != loop;
--count, dst = src, src += (src < n - delta ? delta : delta - n))
A[dst] = std::move(A[src]);
A[dst] = std::move(tmp);
}
}
Run Code Online (Sandbox Code Playgroud)
如前所述,这依赖于具有随机访问迭代器的容器。
请注意,我们可以通过使用交换来消除对临时存储的需求,就像上面第一个算法的替代版本一样。如果我们这样做,那么我们就可以并行执行所有外部循环,因为它们不会相互干扰。因此,我们可以一次只向前移动一个元素,将每个元素与其 Δ-下一个对等元素交换。
这导致作为提供旅游德力样品实施std::rotate。它正好执行 N 次交换,这可能比上面的解决方案效率稍低(N + gcd(N, Δ) 移动),但只需要前向迭代器和交换:(下面的代码稍作修改以更好地符合上面的例子。)
template <class Iter>
void rotate(Iter first, Iter newfirst, Iter last) {
if(first == newfirst || newfirst == last) return;
Iter next = newfirst;
do {
std::iter_swap(first++, next++);
if (first == newfirst) newfirst = next;
} while (next != last);
for(next = newfirst; next != last; ) {
std::iter_swap(first++, next++);
if (first == newfirst) newfirst = next;
else if (next == last) next = newfirst;
}
}
Run Code Online (Sandbox Code Playgroud)
上面唯一棘手的部分是环绕的处理。请注意,first和之间的(循环)距离next始终相同(Δ)。newfirst用于跟踪环绕点:每次first到达newfirst,newfirst前进 Δ(通过将其分配给 的值next,始终为 Δ 超出first)。
next在第一个循环结束时第一次环绕。一旦发生这种情况,它就在容器末端的Δ内;第二个循环继续交换。在这个过程中,它使用欧几里德算法有效地计算了 N 和 Δ 的 GCD。