数组旋转问题的讨论

问题:将一个数组a中的元素向左旋转i个位置。

这个问题看似比较简单。但是在许多的应用程序中以各种不同的伪装出现。并且该功能也是向量的一个基本操作。对于此问题,本文中将给出三种解决方法。

方法一

首先将a的前i个元素复制到一个临时数组中,然后将余下的n-i个元素向左移动i个位置,最后将最初的i个元素从临时数组中复制到a中的余下位置。

void convert1(int a[], int n, int m)
{
	int *b = new int[m];                 //创建临时数组
	for (int i = 0; i < m; i++)          //将前m个元素复制到临时数组中
		*(b + i) = a[i];
	for (int i = m; i < n; i++)          //将余下的n-m个元素向左移动m个位置
		a[i - m] = a[i];
	for (int i = 0; i < m; i++)          //将最初的m个元素从临时数组中复制到a中余下的位置
		a[n-m + i] = b[i];
	delete[]b;                           //记得回收临时数组的内存
}

int main()
{
	int a[10];
	for (int i = 0; i < 10; i++)
		a[i] = i;

	convert1(a, 10, 5);

	for (int i = 0; i < 9; i++)
		cout << a[i] << ",";
	cout << a[9] << endl;
	return 0;
}

此方法中使用的i个额外的位置产生了过大的存储空间的消耗。当我的内存空间是非常宝贵的情况下,此方法是不可行的。

方法二

移动a[0]到临时变量temp,然后移动a[0+i]至a[0],移动a[0+2*i]至a[0+i],依次类推。当数组的下标大于数组长度时,将当前下标减去数组长度,直至返回到取a[0]的值,此时改为从temp取值然后结束当前循环。如果该过程没有移动全部元素,就从a[1]开始再次移动,直至所有的元素全部被移动。

int gcd(int a, int b)                                  //求解向左偏移量i和数组a长度的最大公约数
{
	int c;
	c = (a>b) ? b : a;
	while (a%c != 0 || b%c != 0)
	{
		c--;
	}
	return c;
}
void convert2(int a[],int n,int m)
{
	int temp;                              //临时变量temp声明
	for (int i = 0; i < gcd(m,n); i++)
	{
		int temp = a[i];               //将a[0]移动至临时变量temp中
		int j=i;
		for (; ;)
		{
			int k = j + m;
			if (k>=n)             //当数组的下标大于数组长度时,将当前下标减去数组长度
				k -= n;
			if (k == i)          //当取a[0]的值值,当前循环结束
				break;
			a[j] = a[k];           //将a[0+i]移动至a[0]中
			j = k;
		}	
		a[j] = temp;                //将temp中的值移动至a[0+n*i]中
	}
}

int main()
{
	int a[10];
	for (int i = 0; i < 10; i++)
		a[i] = i;

	convert2(a, 10, 5);

	for (int i = 0; i < 9; i++)
		cout << a[i] << ",";
	cout << a[9] << endl;
	return 0;
}

此方法虽然消耗内存空间短,运行时间也不长。但在我们理解编写程序过程中比较困难,编写的代码比较长。

方法三

我们将数组a的前i个元素看做向量m,将余下的元素看做向量n,数组a就可以表示成为mn。那么我们将数组a向左偏移i位其实就是将mn转化为nm。对于mn,首先对n求逆,然后对m求逆,最后对整体求逆,即可将mn转换为nm。

void convert3(int a[], int n, int m)
{
	if ((m - n) % 2 == 0)
	{
		for (int i = 0; i < (m - n) / 2; i++)
		{
			a[m - i] = a[m - i] ^ a[n + i];
			a[n + i] = a[n + i] ^ a[m - i];
			a[m - i] = a[m - i] ^ a[n + i];
		}
	}
	else{
		for (int i = 0; i <=(m - n) / 2; i++)
		{
			a[m - i] = a[m - i] ^ a[n + i];
			a[n + i] = a[n + i] ^ a[m - i];
			a[m - i] = a[m - i] ^ a[n + i];
		}
	}
		
}

int main()
{
	int a[10];
	for (int i = 0; i < 10; i++)
		a[i] = i;
	
	convert3(a, 0, 4);                       //对m求逆
	convert3(a, 5, 9);                       //对n求逆
	convert3(a, 0, 9);                       //对整体求逆
	
	for (int i = 0; i < 9; i++)
		cout << a[i] << ",";
	cout << a[9] << endl;
	return 0;
}

此方法在时间和空间上都很高效,而且代码非常简短,不容易出错。

对于翻转的理解,可以参考一个非常经典的例子:Doug McIlroy给出的将十元数组向上选转5个位置的翻手例子。

源码下载链接:https://github.com/XiaoYaoNet/Array-Convert

猜你喜欢

转载自blog.csdn.net/qq_38697681/article/details/80382020