C语言实现数组元素循环右移
算法设计要求
试设计一个算法,将数组 A n A_n An中的元素全部循环向右移动k位,并要求只用一个元素大小的附加存储,元素移动或者交换次数为 O ( n ) O(n) O(n)。
算法分析思想
将数组分为两部分,前n-k个元素和后k个元素,分别将这两部分反转,然后再将整个数组反转。这样可以实现循环右移的效果,而且不需要使用递归。
数学分析
首先我们来看优化算法的实现过程:
首先对k进行取模,以处理k大于n的情况。
然后反转前n-k个元素,将前n-k个元素从[0, n-k-1]反转为[n-k-1, 0],此时数组变为了[An-k, An-k+1, …, An-1, A0, A1, …, An-k-1]。
接着反转后k个元素,将后k个元素从[n-k, n-1]反转为[n-1, n-k],此时数组变为了[An-k, An-k+1, …, An-1, An-k, An-k+1, …, An-1-k]。
最后反转整个数组,将整个数组从[0, n-1]反转为[n-1, 0],此时数组变为了[An-k, An-k+1, …, An-1-k, A0, A1, …, An-k-1],即将所有元素循环右移了k位。
接下来我们用数学方法来分析为什么这样可以。
设原数组为A,将A循环右移k位后得到的新数组为B。
根据循环右移的定义,可以将B表示为:
B[i] = A[(i+k) mod n] (0 <= i < n)
我们将B中的元素拆分成两部分:前n-k个元素和后k个元素,分别为B1和B2。
B1[i] = A[(i+k) mod n] (0 <= i < n-k)
B2[i] = A[(i+k) mod n] (n-k <= i < n)
将B1和B2反转后得到的新数组为C1和C2。
C1[i] = A[(n-k-1-i+k) mod n] (0 <= i < n-k)
C2[i] = A[(n-1-i+k) mod n] (n-k <= i < n)
合并C1和C2得到新数组C。
C[i] = A[(n-k-1-i+k) mod n] (0 <= i < n-k)
C[i] = A[(n-1-i+k) mod n] (n-k <= i < n)
将C反转后得到的新数组D即为循环右移k位后的数组。
D[i] = A[(n-k-1-i+k+k) mod n] (0 <= i < n)
化简上式可得:
D[i] = A[(n-k-1-i) mod n] (0 <= i < n)
可见,通过将数组分为前n-k个元素和后k个元素,分别反转,然后再反转整个数组,可以实现将数组循环右移k位的效果。
代码实现
#include <stdio.h>
void reverse(int a[], int start, int end);
void MoveRight(int a[], int n, int k);
int main() {
int a[5] = {1, 2, 3, 4, 5};
MoveRight(a, 5, 3);
for (int i = 0; i < 5; i++) {
printf("%d ", a[i]);
}
return 0;
}
/// @brief 将start和end中的元素进行反转(包含start和end这两个元素)
/// @param a
/// @param start
/// @param end
void reverse(int a[], int start, int end) {
while (start < end){
int temp = a[start];
a[start] = a[end];
a[end] = temp;
start++;
end--;
}
}
/// @brief 将数组中的所有元素全部循环右移k位
/// @param a
/// @param n 数组元素的总数
/// @param k 右移的距离
void MoveRight(int a[], int n, int k) {
k %= n;
reverse(a, 0, n - k - 1);
reverse(a, n - k, n - 1);
reverse(a, 0, n - 1);
}