数组循环移位问题

本文介绍了两种数组循环移位的算法:三次反转算法和排列循环算法。三次反转算法通过三次反转操作实现元素的循环移动,而排列循环算法则利用最大公约数(gcd)确定循环子群,直接移动元素到目标位置,降低了时间复杂度。这两种方法都提供了高效的循环移位解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

有关数组循环移位问题,在 借鉴文章中了解了三个解决方法.
首先第一个循环换位算法,就不多说了.
我们从第二个算法开始
算法优势参考: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,2
k,…,(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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值