问题:将一个数组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个位置的翻手例子。