这就是我想要做的: 数组A [] = {1,2,3,4,5} 向左旋转2:A:{3,4,5,1,2}
我们是否有一个简单而良好的解决方案来执行此操作?我希望数组A本身使用此向左旋转的值进行更新-没有额外的空间。
我尝试了各种方法,但是对于各种测试用例,逻辑似乎有所不同,并且很难找到一种适合此看似简单任务的算法。
注意:我知道,只需使用左旋转值创建一个新数组就可以轻松完成此操作。我正在尝试在输入数组本身中执行此操作。
建议。简单的伪代码应该可以。
答案 0 :(得分:4)
std::rotate()将完全满足您的需求:
auto b = std::begin(A);
std::rotate( b, b + 2, std::end(A) );
答案 1 :(得分:3)
向量旋转似乎是神秘算法的特别沃土。其中大多数都可以在野外发现,且种类繁多。所有有效率的人都需要一些思想来理解其功能。
如果仅向左旋转一个元素,则可以非常高效地完成此操作:
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);
}
}
您可以通过执行delta
次(更准确地说,Δ
)来使用它来旋转Δ % N
个位置,但这需要时间O(NΔ),即O(NΔ) 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);
}
}
交换通常比移动更多的工作,但是可能有一个特定于容器的有效交换实现。无论如何,此版本将有助于理解以后的实现。
一个众所周知且经常被引用的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);
}
我认为这真的很优雅。您可以在纸上尝试一下以了解其工作原理:
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
这是众所周知的“颠倒句子中单词的顺序”解决方案的特例,该解决方案包括首先颠倒每个单词的字母,然后颠倒整个字符串。
但是,它有两个问题。首先,当一次移动就足够时,它将(几乎)每个元素移动两次。其次,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
使用随机访问迭代器更容易(这是一个缺陷);这是一个示例实现。 (变量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);
}
}
如前所述,它依赖于具有随机访问迭代器的容器。
请注意,我们可以通过使用交换消除对临时存储的需求,就像上面第一个算法的替代版本一样。如果这样做,那么我们可以并行执行所有外部循环,因为它们不会互相干扰。因此,我们可以一次向前移动一个元素,然后将每个元素与其Δ-next对等交换。
这导致了the sample implementation of 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;
}
}
上面唯一棘手的部分是环绕处理。请注意,first
和next
之间的(循环)距离始终相同(Δ)。 newfirst
用于跟踪环绕点:每次first
到达newfirst
时,newfirst
前进Δ(将其分配给next
的值,它总是比first
的Δ)。
next
在第一个循环结束时第一次环绕。一旦发生这种情况,它在容器末端的Δ之内;第二个循环继续交换。在此过程中,利用欧几里得算法可以有效地计算出N和Δ的GCD。
答案 2 :(得分:1)
该技术称为旋转。
让我们假设数组中的0元素为左边缘:
0 1 2 3 4 5 <-- array indices
+---+---+---+---+---+---+
| 3 | 1 | 4 | 1 | 5 | 9 |
+---+---+---+---+---+---+
该操作为A[0] = A[1]
或A[i] = A[i + 1]
。
两个例外是第一个插槽和最后一个插槽。
首先,将左侧元素复制到一个临时变量中:
temp 0 1 2 3 4 5 <-- array indices
+---+ +---+---+---+---+---+---+
| 3 |<-- | 3 | 1 | 4 | 1 | 5 | 9 |
+---+ +---+---+---+---+---+---+
接下来,复制一个剩下的所有剩余元素:
temp 0 1 2 3 4 5 <-- array indices
+---+ +---+---+---+---+---+---+
| 3 |<-- | 1 | 4 | 1 | 5 | 9 | 9 |
+---+ +---+---+---+---+---+---+
最后,将临时变量复制到最后一个插槽:
0 1 2 3 4 5 temp
+---+---+---+---+---+---+ +---+
| 1 | 4 | 1 | 5 | 9 | 3 | <-- | 3 |
+---+---+---+---+---+---+ +---+
向右旋转会在另一个方向起作用:
A[i - 1] = A[i]
编辑1
要旋转一个以上的位置,请旋转两次,或修改算法以跳过元素。
例如,向左旋转2:A[i] = A[i + 2]
。
将临时存储留给读者练习。 :-)
答案 3 :(得分:1)
我尝试了O(n)的解决方案。在这里,我制作了另一个相同大小的向量。假设您想向左旋转2,因此d = 2。首先将元素从位置2复制到新数组中的位置0,直到结尾。然后将元素从第一个数组的开头复制到第二个数组的末尾,即从n维位置复制。
int i = 0;
int r = d;
vector<int> b(n);
for(i=0; i< n-d; i++)
{
b[i] = a[r];
r++;
}
r = 0;
for(i=n-d; i<n; i++)
{
b[i] = a[r];
r++;
}
答案 4 :(得分:0)
毕竟可以做到。
@ThomasMatthews建议的内容可以作为起点:您可以简单地开始交换array[i]
和array[i+rotate]
的元素,最多可以交换i=0...last-rotate
。问题在于,除非数组的长度是rotate
的整数倍,否则最后rotate
个元素的顺序将很杂乱,尽管这种情况看起来很好我不知道为什么(这只是第一次出现)。
使用此代码段,您可以交互式检查这些元素的外观,并且您可能会注意到,其余元素需要length of array % rotate
(即结尾处的单个数字)的右移量才能变得正确。 。我当然不知道为什么。
function test(){
var count=document.getElementById("count").valueAsNumber;
var rotate=document.getElementById("rotate").valueAsNumber;
var arr=[...Array(count).keys()];
for(var i=0;i<count-rotate;i++){
var t=arr[i];
arr[i]=arr[i+rotate];
arr[i+rotate]=t;
}
document.getElementById("trivial").innerHTML=arr.slice(0,count-rotate).join();
document.getElementById("tail").innerHTML=arr.slice(count-rotate).join();
document.getElementById("remainder").innerHTML=count%rotate;
}
test();
<input type="number" id="count" oninput="test()" min="1" value="41"><br>
<input type="number" id="rotate" oninput="test()" min="1" value="12"><br>
<div id="trivial"></div>
<div id="tail"></div>
<div id="remainder"></div>
然后,我还添加了一些递归,还利用了可以将右旋转作为左旋转的事实(必须从元素数量中减去移位量),所以我不必编写单独的方法,因为我很懒。块显示在单独的行中(部分结果正常显示,输入的块用括号括起来),因此更容易理解。在这里,我使用array.slice()
创建一个新数组,但是可以通过传递起始索引和长度来代替它,因此它可以在C / C ++中用作就地操作。
function rot(arr,rotate){
var retbase="("+rotate+":"+arr.join()+")<br>"; // input in parentheses
for(var i=0;i<arr.length-rotate;i++){
var t=arr[i];
arr[i]=arr[i+rotate];
arr[i+rotate]=t;
}
var rightrot=arr.length % rotate; // amount of right-rotation missing
if(rightrot===0) // done
return retbase+arr.join();
else{ // needs fixing
retbase+=arr.slice(0,arr.length-rotate)+"<br>"; // partial result
arr=arr.slice(arr.length-rotate);
return retbase
+rot(arr,arr.length-rightrot); // flipping right-rotation left-rotation
}
}
function test(){
var count=document.getElementById("count").valueAsNumber;
var rotate=document.getElementById("rotate").valueAsNumber;
var arr=[...Array(count).keys()];
document.getElementById("result").innerHTML=rot(arr,rotate);
}
test();
<input type="number" id="count" oninput="test()" min="1" value="41"><br>
<input type="number" id="rotate" oninput="test()" min="1" value="12"><br>
<div id="result"></div>
递归的深度/模式与GCD计算有些相似,因此,如果有人不得不说一些复杂性,我会开始寻找这个方向。