描述:将一个长度为 n 的数组 A 的元素循环右移(ROR, Rotate Right)k 位,比如数组 1, 2, 3, 4, 5 循环右移 3 位之后就变成 3, 4, 5, 1, 2。
要求:只能用一个元素大小的附加存储,元素移动或交换次数为O(n)。
开始时自己想的算法就是最简单原始的一种:
每次将数组右移一位,循环k次:
void function(int *A,int n,int k){
int tmp=A[n-1];
for(int i=0;i<k;i++){
for(int j=1;j<=n;j++){
A[j]=A[j-1];
}
A[0]=tmp;
}
}
但这个方法的时间复杂度是O(n*k),不符合题目要求,有没有更好的算法呢?
google了发现实现这个的是经典的“三步反转法”:
记 A 的前 n-k 位为 X,后 k 位为 Y,则 A=XY,将 A 循环右移 k 位后,应该得到YX。根据该算法,先将 A 整体倒置,得到 (XY )^T = Y^T • X^T,然后将前 k 位倒置,得到 Y • X^T,最后将后n-k 位倒置,得到 YX,正好是所求的结果。
这样我们可以将数组分为两段,先将它们全部倒置,再将前k位倒置,再将后n-k位倒置,即 1, 2, 3, 4, 5 变成 5, 4, 3, 2, 1,然后将前 k 位倒置,即 3, 4, 5, 2, 1,再将后n-k 位倒置,即 3, 4, 5, 1, 2,完成。
static void swap(int array[], int i, int j) {
const int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
static void reverse(int array[], int begin, int end) {
end--;
while (begin < end)
swap(array, begin++, end--);
}
void ror3(int *array, int n, int k) {
k %= n;
if (k == 0)
return;
reverse(array, 0, n);
reverse(array, 0, k);
reverse(array, k, n - k);
}