有关数组循环移位问题,在 借鉴文章中了解了三个解决方法.
首先第一个循环换位算法,就不多说了.
我们从第二个算法开始
算法优势参考:https://www.cnblogs.com/cs-whut/p/10975293.html
三次反转算法:
三次反转算法是通过三次反转来完成循环移位的,比如数组[1,2,3,4,5],然后向左移三步,可以通过以下函数完成:
reverse(0,2) ->[3,2,1,4,5]
reverse(2,4) ->[3,2,1,5,4]
reverse(0,4) ->[4,5,1,2,3]
若把移步数记为p,数组有n个数,则有:
reverse(0,p-1)
reverse(p,n-1)
reverse(0,n-1)
确实我们在看移位前,移位后的结果时,会发现移步数将数组分成了两个部分,然后两个部分发生了位置的调换,而三次反转后达成的位置调换原理可以配合着翻掌算法来理解:
三次反转–>翻掌算法:
初始时掌心对着我们的脸,左手在右手上边,先将左手反转(掌心朝外,掌背朝我们的脸),再将右手翻转(同左手),最后两只手同时翻转,这时会发现,两只手依旧是掌心对着我们,而手的叠加次序却改变了.
//利用三次反转实现循环移位
public class CircleGo_1 {
public static void reverse(int nums[],int a, int b) {
int middle;
//注意反转,是从区间第一个到最后一个数反转,中间数不变
for(int i=0;i<(b-a+1)/2;i++) {
middle=nums[a+i];
nums[a+i]=nums[b-i];
nums[b-i]=middle;
}
}
public static void main(String[] args) {
int nums[]= {1,2,3,4,5};
int p =3,n=nums.length;
reverse( nums, 0,p-1);
reverse(nums, p,n-1);
reverse(nums,0,n-1);
System.out.println(Arrays.toString(nums));
}
}
排列循环算法:
在普通的循环换位算法中,需要移动几步,就要整体循环几次,每次移动一步,这样的空间复杂度是O(1),时间复杂度是O(nk)。如果k小于n,则O(n^2)。
那么,有没有可能直接把要移动的数移动到循环后的位置呢,这样,每个数只需移动一次就完成循环移位,那么如果可能,怎么确定要移动的具体位置呢.
比如序列下标为[i]的数,其循环移位k步,那就应该在[i+k]的位置,[i+k]位置的数就应该在[i+2k]的位置,由于每个元素右移n位后又回到了原来的位置上,所以右移k位等于右移k mod n位.如此就有下面这样一个位置序列:0,k,2k,…,(t-1)k。其中每一项都是在模n的意义下的位置。tk mod n 的结果是0。t是使得tk mod n的结果为0的最小正整数。且t是必然存在的,最大是在t等于n的时候,如此就形成了一个环.
准确的说就形成了一个循环群.
这个位置序列实质上是模n加法群中由元素k生成的一个循环子群。由群论中的结论(该结论的证明见最后)知,循环子群(k)的周期为n / gcd(k,n),元数为n / gcd(k,n),其陪集个数为gcd(k,n)。换句话说,A[0…n-1]循环右移k位的结果是循环子群(k)以及它的所有陪集循环右移一位。例如,将A[0…5] = {1,2,3,4,5,6}循环右移4位,这里n = 6, k = 4, gcd(k, n) = 2。A[0]的最终位置是A[4],A[4]的最终位置是A[2],A[2]的最终位置是A[0],这样,位置0,4,2便是由k=4生成的循环群,周期为6 / gcd(4,6) = 6 / 2 = 3,这样的循环子群共有gcd(4,6) = 2个。
如此完整代码就有,先确定gcd(k,n)循环子群个数,然后逐个循环群中元素移位.
// 数组的循环移位
#include <cstdio>
//gcd(x,y)表示x和y的最大公因数--欧几里德算法
int gcd(int m, int n) {
int r;
//r记录余数,除数不断除以余数,直到余数为0,此时除数就是最大公因数
while (r = m % n) {
m = n;
n = r;
}
return n;
}
void shiftArray(int A[], int n, int k) {
// 因为左移的代码比右移的代码好实现的多,而右移k位
// 等价于左移-k位,通俗来说右移k位,等于左移n-k位.以下代码是通过左移n-k位来实现右移k位
k = n - (k % n);
for (int i = 0, cnt = gcd(n, k); i < cnt; i++) {
int t = A[i], p = i, j = (k + i) % n;
while (j != i) {
A[p] = A[j]; p = j; j = (k + p) % n;
}
A[p] = t;
}
}
void printArray(int A[], int n) {
for (int i = 0; i < n; i++) {
printf("%-3d", A[i]);
}
}
int main() {
int A[] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };
printf("向右循环移项12步\n");
shiftArray(A, 15, 12);
printArray(A, 15);
return 0;
}