zoukankan      html  css  js  c++  java
  • Java实现基于数组的向量旋转的四种算法

          摘要:Java实现基于数组的向量旋转的四种算法。包括:基于数组移动的思路;基于跳跃交换元素的思路; 基于数组区域交换的思路:AB---> BA;基于数组逆置的思路。

         难度: 初级。

          向量旋转问题:给定一个 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 89 10] ,  n=10, I = 3 ; 


               S1: A=[1,2,3] , B=[4,5,6,7,8,9,10] ; A < B.

               根据(2)---> [1 2 3 | 4 5 6 7 | 8 9 10] --->  [89 10 | 4 5 6 7 | 1 2 3]

           n = 7, I = 3; [1 2 3]已在最终位置;

               第一趟结果:[89 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] 已在最终位置;

                第二趟结果:[56 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 *67 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 78 9 10 1 2 3]

     

     

               4. 基于数组逆置的思路

             一个非常优雅而简单的公式:(arbr)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]

     

           Java 实现代码:

           

    /**
     * VectorRotation  @author shuqin1984 2011-04-18
     * 
     * 本程序主要实现四种向量旋转算法; 
     * 向量旋转问题: 将给定 n 维向量最左边的 i 位依次移动到向量最右边.
     * 比如,[1,2,3,4,5] 左移 3 位, 变成 [4,5,1,2,3]
     * 数据结构与算法描述
     * 1. 向量使用数组来表示;
     * 2. 四种算法如前所述,分别基于"移动数组元素", "跳跃交换元素", "交换数组区域", "数组逆置" 四种思路。
     */
    package algorithm.vector;
    import java.util.Arrays;
    public class VectorRotation {
        
        private VectorRotation() { }
        
        /**
         * leftShift4: 基于移动数组元素的思路实现向量旋转.
         */
        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;
        }
        
        /**
         * leftShift3 : 基于跳跃交换元素求解向量旋转问题
         */
        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;
        }
        
        /*
         * gcd: 求给定两数的最大公约数
         */
        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);
        }
        
        /**
         * leftShift2 : 基于交换数组区域求解向量旋转问题
         */
        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) {  // AB -> BA ; 所要左移的位数正好是数组长度的一半,只要将数组左右等长区域交换即可
                    exchange(arr, beginIndex, beginIndex + shiftBits, shiftBits);
                    break;
                } else if (varlength > 2 * shiftBits) { // ABlBr -> BrBlA ; 所要左移的位数小于数组长度的一半,将右边区域分成两份,并将其右半部分与左边区域交换
                    exchange(arr, beginIndex, varlength-shiftBits, shiftBits);
                    endIndex -= shiftBits;
                } else if (varlength < 2 * shiftBits) { // AlArB -> BArAl ; 所要左移的位数大于数组长度的一半,将左边区域分成两份,并将其左半部分与右边区域交换
                    exchange(arr, beginIndex, beginIndex + shiftBits, varlength - shiftBits);
                    beginIndex += varlength - shiftBits;
                    shiftBits = 2 * shiftBits - varlength;
                }
                varlength = endIndex - beginIndex + 1;
            }
            return arr;
        }
        
        /*
         * exchange: 将指定的数组区域内容互换,具体来说,
         * arr[beginIndex1:beginIndex1+length-1] 与 arr[beginIndex2, beginIndex2+length-1] 的内容依次互换,即
         * arr[beginIndex1] 与 arr[beginIndex2] 互换, ... arr[beginIndex1+length-1] 与 arr[beginIndex2+length-1] 互换.
         * 前置条件: 指定数组区域不能超过数组范围,且区域互不重叠 
         * 
         */
        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("参数错误,指定数组区域不能重叠!");
        }
        
        /**
         * leftShift: 基于数组逆置法求解向量旋转问题
         */
        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;
        }
        
        /**
         * reverse: 将数组的指定区域逆置
         * @param beginIndex 指定区域的起始下标
         * @param endIndex 指定区域的终止下标(包含)
         */
        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 + "] 错误,第一个参数必须不大于第二个参数!");
            }
        }
        
        /*
         * processParameters: 进行参数处理,若不合法,抛出异常;若合法,返回实际需要移位的位数
         */
        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);    
                
            }
        }
    } 

          

  • 相关阅读:
    HTMLDOM
    换行
    【iOS】APP之数据存储
    开启远程XUL
    iOS之Streams
    Plugin的生命周期
    ActiveX Control Test Container
    ObjectiveC Runtime III【objc_msgSend函数】
    What is a Digital Signature?
    JS变量作用域
  • 原文地址:https://www.cnblogs.com/lovesqcc/p/4037847.html
Copyright © 2011-2022 走看看