向量旋转问题: 给定一个 n 维向量, 求 将它向左循环移动 i 位后的向量。比如: [1,2,3,4,5] 向左循环移动 3 位后,变成 [4,5,1,2,3] 。为了简单起见,向量采用数组表示。
本文讨论的内容参见《编程珠玑 I 》 ( 第二版 ) 的第二章。在那里,讨论了向量旋转的四种算法:
1. 基于数组移动的思路: 这个是比较简单的, 即将要移动的 i 个元素复制到一个临时数组中,然后,将原数组的 n-i 个元素依次复制到前 n-i 个位置上,最后,将临时数组中的 i 个元素移动到原数组的末尾 i 个位置上。该思路实现简单,运行时间效率为 O(n) , 空间效率是 O(i) ;当 i 较大时,会有较大的空间消耗。
图示: [1,2,3,4,5] ---> [4,5,3,4,5] ---> [4,5,1,2,3]
|---> 临时数组: [1,2,3] -------- >|⇑
2. 基于跳跃交换元素的思路:
实际上,也是比较直观的。 例如, [1,2,3,4,5] , I = 3. 直观的想法, 4 肯定要到 1 的位置上; 那么谁到 4 的位置上呢? 这需要将数组想像成一个环形(类似循环队列),在逻辑上通常是取模操作。 [1,2,3,4,5,1,2,3,4,5] ,显然, 2 到 4 的位置。 2 = (4+3) % 5. 接着, 5 到 2 的位置; (5+3) % 5 = 3 到 5 的位置;(3+3) % 5 = 1 到 3 的位置。这样形成了一个跳跃链: [1<4<2<5<3<1]
即: arr[1] = arr[4] ; arr[4] = arr[2]; arr[2] = arr[5]; arr[5] = arr[3]; arr[3] = arr[1]. 这样跳跃交换后,得到最终结果: [4,5,1,2,3]
当 n 与 I 具有大于 1 的最大公约数时,情形略有所不同。 例如, [1,2,3,4,5,6] , I = 4. 需要分两轮( 轮数是 n 与 i 的最大公约数 ):
S1: arr[1] = arr[5] , arr[5] = arr[3], arr[3] = arr[1] ,
S2: arr[2] = arr[6], arr[6] = arr[4], arr[4] = arr[2].
至此,也得到最终结果: [5,6,1,2,3,4]
后面两种思路基于同一个观察结果: 向量旋转实际上就是将 AB 转换为 BA 的过程。
3. 基于数组区域交换的思路: AB ---> BA
(1) 若 A 与 B 长度相等,则将数组区域 A 和 B 交换即可;
(2) 若 A 的长度小于 B ,则将 B 分成两部分 BlBr 其中 Br 的长度与 A 相等。则 AB = ABlBr. 交换数组区域 A 与 Br , 得到 BrBlA ,此时, A 已经在最终位置。问题转换为: 将向量 BrBl 左移 length(Br) 位;即原问题的更小规模形式,可递归求解;
(3) 若 A 的长度大于 B ,则将 A 分成两部分 AlAr ,其中, Al 的长度与 B 相等。则 AB = AlArB. 交换数组区域 Al 与 B ,得到 BArAl ,此时, B 已经在最终位置上,问题转换为:将向量 ArAl 左移 length(Ar)位;即原问题的更小规模形式,可递归求解。
图示: [1 2 3 4 5 6 7 8 9 10] , n=10, I = 3 ;
S1: A=[1,2,3] , B=[4,5,6,7,8,9] ; A < B.
根据 (2) ---> [1 2 3 | 4 5 6 7 | 8 9 10] ---> [8 9 10 | 4 5 6 7 | 1 2 3]
n = 7, I = 3; [1 2 3] 已在最终位置;
第一趟结果 : [ 8 9 10 4 5 6 7 * 1 2 3] ; n = 7; I = 3
S2: A = [8,9,10] , B= [4,5,6,7] ; A < B
根据 (2) ---> [8 9 10 | 4 | 5 6 7] ---> [5 6 7 | 4 | 8 9 10]
n = 4, I = 3; [8,9,10] 已在最终位置;
第二趟结果: [ 5 6 7 4 * 8 9 10 1 2 3] ; n = 4, I = 3
S3: A=[5,6,7] , B = [4] ; A > B
根据 (3) ---> [5 | 6,7 | 4] ---> [4 | 6,7 | 5]
n = 3, i = 2 ; [4] 已在最终位置。
第三趟结果: [4 * 6 7 5 * 8 9 10 1 2 3] ; n = 3, I = 2
S4: A=[6,7] , B= [5] ; A > B
根据 (3) ---> [6 | 7 | 5] ---> [5 | 7 | 6]
n = 2, I = 1; [5] 已在最终位置
第四趟结果: [4 5 * 7 6 * 8 9 10 1 2 3] ; n = 2, I = 1
S5: A= [7] , B= [6] ; A=B
根据 (1) ---> [6,7] 算法结束。至此所有元素都在其位置上。
第五趟结果: [4 5 6 7 8 9 10 1 2 3]
4. 基于数组逆置的思路: 一个非常优雅而简单的公式: (ar br )r = ba ,类似于对偶律,可用数学归纳法证明。这意味着,只要将 a 部分逆置,然后将 b 部分逆置,最后将整个部分逆置,就得到了所期望的结果。算法简单,优雅,并且高效,不易出错;时间效率是 O(n) ,空间效率是 O(1) 。这说明,掌握一定的计算机科学知识和方法对于程序设计是非常重要的。
图示: [1,2,3,4,5] ---> [3,2,1,4,5] ---> [3,2,1,5,4] ---> [4,5,1,2,3]
-
-
-
-
-
-
-
-
-
-
- package algorithm.vector;
- import java.util.Arrays;
- public class VectorRotation {
-
- private VectorRotation() { }
-
-
-
-
- public static int[] leftShift4(int[] arr, int i)
- {
- int shiftBits = processParameters(arr, i);
- if (shiftBits == 0) {
- return arr;
- }
-
- int arrlen = arr.length;
- int[] temp = new int[shiftBits];
- for (int k=0; k < shiftBits; k++) {
- temp[k] = arr[k];
- }
- for (int k=shiftBits; k < arrlen; k++) {
- arr[k-shiftBits] = arr[k];
- }
- for (int k = 0; k < shiftBits; k++) {
- arr[k + arrlen-shiftBits] = temp[k];
- }
- return arr;
- }
-
-
-
-
- public static int[] leftShift3(int[] arr, int i)
- {
- int shiftBits = processParameters(arr, i);
- if (shiftBits == 0) {
- return arr;
- }
- int arrlen = arr.length;
- for (int k=0; k < gcd(arr.length, shiftBits); k++) {
- int temp = arr[k];
- int foreIndex = k;
- int afterIndex = k + shiftBits;
- while (afterIndex != k) {
- arr[foreIndex] = arr[afterIndex];
- foreIndex = (foreIndex + shiftBits) % arrlen;
- afterIndex = (afterIndex + shiftBits) % arrlen;
- }
- arr[foreIndex] = temp;
- }
- return arr;
- }
-
-
-
-
- private static int gcd(int m, int n)
- {
- if (m < 0 || n < 0) {
- throw new IllegalArgumentException("参数错误,必须均是正整数!");
- }
- if (m % n == 0) {
- return n;
- }
- return gcd(n, m%n);
- }
-
-
-
-
- public static int[] leftShift2(int[] arr, int i)
- {
- int shiftBits = processParameters(arr, i);
- if (shiftBits == 0) {
- return arr;
- }
- int beginIndex = 0;
- int endIndex = arr.length-1;
- int varlength = endIndex - beginIndex + 1;
- while (true) {
- if (varlength == 2 * shiftBits) {
- exchange(arr, beginIndex, beginIndex + shiftBits, shiftBits);
- break;
- } else if (varlength > 2 * shiftBits) {
- exchange(arr, beginIndex, varlength-shiftBits, shiftBits);
- endIndex -= shiftBits;
- } else if (varlength < 2 * shiftBits) {
- exchange(arr, beginIndex, beginIndex + shiftBits, varlength - shiftBits);
- beginIndex += varlength - shiftBits;
- shiftBits = 2 * shiftBits - varlength;
- }
- varlength = endIndex - beginIndex + 1;
- }
- return arr;
- }
-
-
-
-
-
-
-
-
- private static void exchange(int[] arr, int beginIndex1, int beginIndex2, int length)
- {
- checkParametersForExchange(arr, beginIndex1, beginIndex2, length);
- for (int k=0; k < length; k++) {
- int temp = arr[k+beginIndex1];
- arr[k+beginIndex1] = arr[k+beginIndex2];
- arr[k+beginIndex2] = temp;
- }
- }
-
- private static void checkParametersForExchange(int[] arr, int beginIndex1, int beginIndex2, int length)
- {
- if (beginIndex1 + length-1 >= arr.length || beginIndex2 + length-1 >= arr.length) {
- throw new IllegalArgumentException("参数错误,指定数组区域超过数组范围!");
- }
- if (Math.abs(beginIndex1 - beginIndex2) + 1 <= length)
- throw new IllegalArgumentException("参数错误,指定数组区域不能重叠!");
- }
-
-
-
-
- public static int[] leftShift(int[] arr, int i)
- {
- int shiftBits = processParameters(arr, i);
- if (shiftBits == 0) {
- return arr;
- }
- reverse(arr, 0, shiftBits-1);
- reverse(arr, shiftBits, arr.length-1);
- reverse(arr, 0, arr.length-1);
- return arr;
- }
-
-
-
-
-
-
- public static void reverse(int[] arr, int beginIndex, int endIndex)
- {
- checkParameterForReverse(arr, beginIndex, endIndex);
- int length = endIndex - beginIndex + 1;
- for (int k=beginIndex; k < beginIndex + (length+1)/2; k++) {
- int temp = arr[k];
- arr[k] = arr[beginIndex + endIndex -k];
- arr[beginIndex + endIndex -k] = temp;
- }
- }
-
- private static void checkParameterForReverse(int[] arr, int beginIndex, int endIndex)
- {
- if (beginIndex < 0 || endIndex < 0 || beginIndex >= arr.length || endIndex >= arr.length) {
- throw new IllegalArgumentException("指定区域 [" + beginIndex + "," + endIndex + "] 错误, 参数必须均为正整数,且不能超过数组长度 " + arr.length);
- }
- if (beginIndex > endIndex) {
- throw new IllegalArgumentException("指定区域 [" + beginIndex + "," + endIndex + "] 错误,第一个参数必须不大于第二个参数!");
- }
- }
-
-
-
-
- private static int processParameters(int[] arr, int i)
- {
- if (i < 0) {
- throw new IllegalArgumentException("参数错误,指定移位位数必须是正整数!");
- }
- int shiftBits = i % arr.length;
- return shiftBits;
- }
-
- static class Tester {
-
- public static void testLeftShift(int[] arr, int i) {
- System.out.println("将向量 " + Arrays.toString(arr) + " 循环左移 " + i + " 位:/t");
- try {
- int[] copy = Arrays.copyOf(arr, arr.length);
- System.out.println(Arrays.toString(leftShift(copy, i)) + " /t*** leftShift ");
- copy = Arrays.copyOf(arr, arr.length);
- System.out.println(Arrays.toString(leftShift2(copy, i)) + " /t*** leftShift2 ");
- copy = Arrays.copyOf(arr, arr.length);
- System.out.println(Arrays.toString(leftShift3(copy, i)) + " /t*** leftShift3 ");
- copy = Arrays.copyOf(arr, arr.length);
- System.out.println(Arrays.toString(leftShift4(copy, i)) + " /t*** leftShift4 ");
-
- } catch (Exception e) {
- System.out.println(e.getMessage());
- }
- }
-
- public static void testExchange() {
- int[] arr = new int[] {1,2,3,4,5,6,7,8,9};
- for (int i = 1; i <= 6; i++) {
- int[] copy = Arrays.copyOf(arr, arr.length);
- try {
- exchange(copy, 2, 5, i);
- System.out.println("i = " + i + "/t" + Arrays.toString(copy));
- } catch (Exception e) {
- System.out.println(e.getMessage());
- }
- }
- }
-
- public static void testReverse() {
- int[] arr = new int[] {1,2,3,4,5,6,7,8,9};
- for (int i = 0; i < arr.length; i++) {
- int[] copy = Arrays.copyOf(arr, arr.length);
- try {
- reverse(copy, i, arr.length-i);
- System.out.println("i = " + i + "/t" + Arrays.toString(copy));
- } catch (Exception e) {
- System.out.println(e.getMessage());
- }
- }
- }
-
- public static void testGCD()
- {
- int n = 200, m = 100;
- while(n>=-10 && m >=-10) {
- try {
- System.out.println("[" + n + "," + m + "] 的最大公约数是 :" + gcd(m, n));
- } catch (Exception e) {
- System.out.println(e.getMessage());
- }
- n-= 5; m-=3;
- }
- }
-
- public static void main(String[] args)
- {
- System.out.println("************* 最大公约数 **************");
- testGCD();
-
- System.out.println("************* 数组区域内容交换 ****************");
- testExchange();
-
- System.out.println("************* 数组逆置 ****************");
- testReverse();
-
- System.out.println("************* 向量旋转 ****************");
-
- testLeftShift(new int[] {1,2,3,4,5,6,7,8,9,10}, 3);
- testLeftShift(new int[] {1,2,3,4,5,6,7,8,9,10}, 4);
- testLeftShift(new int[] {1,2,3,4,5,6,7,8,9,10}, 8);
- testLeftShift(new int[] {1,2,3,4,5,6,7,8,9,10}, 13);
- testLeftShift(new int[] {1,2,3,4,5,6,7,8,9,10}, 30);
- testLeftShift(new int[] {1,2,3,4,5,6,7,8,9,10}, 0);
- testLeftShift(new int[] {1,2,3,4,5,6,7,8,9,10}, -1);
-
- }
- }
- }