Fastest algorithm for circle shift N sized array for M position

后端 未结 24 2975
温柔的废话
温柔的废话 2020-11-28 05:33

What is the fastest algorithm for circle shifting array for M positions?
For example, [3 4 5 2 3 1 4] shift M = 2 positions should be [1 4 3 4 5 2 3

相关标签:
24条回答
  • 2020-11-28 05:49

    A very simple solution. This is a very fast way, here I use a temp array with the same size or original and attach to the original variable at the end. This method use O(n) temporal complexity and O(n) space complexity and it is very simple to implement.

    int[] a  = {1,2,3,4,5,6};
        int k = 2;
        int[] queries = {2,3};
    
        int[] temp = new int[a.length];
        for (int i = 0; i<a.length; i++)
            temp[(i+k)%a.length] = a[i];
    
        a = temp;
    
    0 讨论(0)
  • 2020-11-28 05:50

    A friend of mine while joking asked me how to shift an array, I came up with this solutions (see ideone link), now I've seen yours, someone seems a bit esoteric.

    Take a look here.

    #include <iostream>
    
    #include <assert.h>
    
    #include <cstring>
    
    using namespace std;
    
    struct VeryElaboratedDataType
    {
        int a;
        int b;
    };
    
    namespace amsoft
    {
        namespace inutils
        {
            enum EShiftDirection
            {
                Left,
                Right
            };
    template 
    <typename T,size_t len>
    void infernalShift(T infernalArray[],int positions,EShiftDirection direction = EShiftDirection::Right)
    {
        //assert the dudes
        assert(len > 0 && "what dude?");
        assert(positions >= 0 && "what dude?");
    
        if(positions > 0)
        {
        ++positions;
        //let's make it fit the range
        positions %= len;
    
        //if y want to live as a forcio, i'l get y change direction by force
        if(!direction)
        {
            positions = len - positions;
        }
    
        // here I prepare a fine block of raw memory... allocate once per thread
        static unsigned char WORK_BUFFER[len * sizeof(T)];
        // std::memset (WORK_BUFFER,0,len * sizeof(T));
        // clean or not clean?, well
        // Hamlet is a prince, a prince does not clean
    
        //copy the first chunk of data to the 0 position
        std::memcpy(WORK_BUFFER,reinterpret_cast<unsigned char *>(infernalArray) + (positions)*sizeof(T),(len - positions)*sizeof(T));
        //copy the second chunk of data to the len - positions position
        std::memcpy(WORK_BUFFER+(len - positions)*sizeof(T),reinterpret_cast<unsigned char *>(infernalArray),positions * sizeof(T));
    
        //now bulk copy back to original one
        std::memcpy(reinterpret_cast<unsigned char *>(infernalArray),WORK_BUFFER,len * sizeof(T));
    
        }
    
    }
    template 
    <typename T>
    void printArray(T infernalArrayPrintable[],int len)
    {
            for(int i=0;i<len;i++)
        {
            std::cout << infernalArrayPrintable[i] << " ";
        }
        std::cout << std::endl;
    
    }
    template 
    <>
    void printArray(VeryElaboratedDataType infernalArrayPrintable[],int len)
    {
            for(int i=0;i<len;i++)
        {
            std::cout << infernalArrayPrintable[i].a << "," << infernalArrayPrintable[i].b << " ";
        }
        std::cout << std::endl;
    
    }
    }
    }
    
    
    
    
    int main() {
        // your code goes here
        int myInfernalArray[] = {1,2,3,4,5,6,7,8,9};
    
        VeryElaboratedDataType myInfernalArrayV[] = {{1,1},{2,2},{3,3},{4,4},{5,5},{6,6},{7,7},{8,8},{9,9}};
        amsoft::inutils::printArray(myInfernalArray,sizeof(myInfernalArray)/sizeof(int));
        amsoft::inutils::infernalShift<int,sizeof(myInfernalArray)/sizeof(int)>(myInfernalArray,4);
        amsoft::inutils::printArray(myInfernalArray,sizeof(myInfernalArray)/sizeof(int));
        amsoft::inutils::infernalShift<int,sizeof(myInfernalArray)/sizeof(int)>(myInfernalArray,4,amsoft::inutils::EShiftDirection::Left);
        amsoft::inutils::printArray(myInfernalArray,sizeof(myInfernalArray)/sizeof(int));
        amsoft::inutils::infernalShift<int,sizeof(myInfernalArray)/sizeof(int)>(myInfernalArray,10);
        amsoft::inutils::printArray(myInfernalArray,sizeof(myInfernalArray)/sizeof(int));
    
    
        amsoft::inutils::printArray(myInfernalArrayV,sizeof(myInfernalArrayV)/sizeof(VeryElaboratedDataType));
        amsoft::inutils::infernalShift<VeryElaboratedDataType,sizeof(myInfernalArrayV)/sizeof(VeryElaboratedDataType)>(myInfernalArrayV,4);
        amsoft::inutils::printArray(myInfernalArrayV,sizeof(myInfernalArrayV)/sizeof(VeryElaboratedDataType));
        amsoft::inutils::infernalShift<VeryElaboratedDataType,sizeof(myInfernalArrayV)/sizeof(VeryElaboratedDataType)>(myInfernalArrayV,4,amsoft::inutils::EShiftDirection::Left);
        amsoft::inutils::printArray(myInfernalArrayV,sizeof(myInfernalArrayV)/sizeof(VeryElaboratedDataType));
        amsoft::inutils::infernalShift<VeryElaboratedDataType,sizeof(myInfernalArrayV)/sizeof(VeryElaboratedDataType)>(myInfernalArrayV,10);
        amsoft::inutils::printArray(myInfernalArrayV,sizeof(myInfernalArrayV)/sizeof(VeryElaboratedDataType));
    
        return 0;
    }
    
    0 讨论(0)
  • 2020-11-28 05:51

    Here is a simple and efficient general in place rotate function in C++, less than 10 lines.

    which is excerpted from my answer on another question. How to rotate an array?

    #include <iostream>
    #include <vector>
    
    // same logic with STL implementation, but simpler, since no return value needed.
    template <typename Iterator>
    void rotate_by_gcd_like_swap(Iterator first, Iterator mid, Iterator last) {
        if (first == mid) return;
        Iterator old = mid;
        for (; mid != last;) {
            std::iter_swap(first, mid);
            ++first, ++mid;
            if (first == old) old = mid; // left half exhausted
            else if (mid == last) mid = old;
        }
    }
    
    int main() {
        using std::cout;
        std::vector<int> v {0,1,2,3,4,5,6,7,8,9};
        cout << "before rotate: ";
        for (auto x: v) cout << x << ' '; cout << '\n';
        int k = 7;
        rotate_by_gcd_like_swap(v.begin(), v.begin() + k, v.end());
        cout << " after rotate: ";
        for (auto x: v) cout << x << ' '; cout << '\n';
        cout << "sz = " << v.size() << ", k = " << k << '\n';
    }
    
    0 讨论(0)
  • 2020-11-28 05:53

    Optimal solution

    Question asked for fastest. Reversing three times is simplest but moves every element exactly twice, takes O(N) time and O(1) space. It is possible to circle shift an array moving each element exactly once also in O(N) time and O(1) space.

    Idea

    We can circle shift an array of length N=9 by M=1 with one cycle:

    tmp = arr[0]; arr[0] = arr[1]; ... arr[7] = arr[8]; arr[8] = tmp;
    

    And if N=9, M=3 we can circle shift with three cycles:

    1. tmp = arr[0]; arr[0] = arr[3]; arr[3] = tmp;
    2. tmp = arr[1]; arr[1] = arr[4]; arr[4] = tmp;
    3. tmp = arr[2]; arr[2] = arr[5]; arr[5] = tmp;

    Note each element is read once and written once.

    Diagram of shifting N=9, M=3

    The first cycle is show in black with numbers indicating the order of operations. The second and third cycles are shown in grey.

    The number of cycles required is the Greatest Common Divisor (GCD) of N and M. If the GCD is 3, we start a cycle at each of {0,1,2}. Calculating the GCD is fast with the binary GCD algorithm.

    Example code:

    // n is length(arr)
    // shift is how many place to cycle shift left
    void cycle_shift_left(int arr[], int n, int shift) {
      int i, j, k, tmp;
      if(n <= 1 || shift == 0) return;
      shift = shift % n; // make sure shift isn't >n
      int gcd = calc_GCD(n, shift);
    
      for(i = 0; i < gcd; i++) {
        // start cycle at i
        tmp = arr[i];
        for(j = i; 1; j = k) {
          k = j+shift;
          if(k >= n) k -= n; // wrap around if we go outside array
          if(k == i) break; // end of cycle
          arr[j] = arr[k];
        }
        arr[j] = tmp;
      }
    }
    

    Code in C for any array type:

    // circle shift an array left (towards index zero)
    // - ptr    array to shift
    // - n      number of elements
    // - es     size of elements in bytes
    // - shift  number of places to shift left
    void array_cycle_left(void *_ptr, size_t n, size_t es, size_t shift)
    {
      char *ptr = (char*)_ptr;
      if(n <= 1 || !shift) return; // cannot mod by zero
      shift = shift % n; // shift cannot be greater than n
    
      // Using GCD
      size_t i, j, k, gcd = calc_GCD(n, shift);
      char tmp[es];
    
      // i is initial starting position
      // Copy from k -> j, stop if k == i, since arr[i] already overwritten
      for(i = 0; i < gcd; i++) {
        memcpy(tmp, ptr+es*i, es); // tmp = arr[i]
        for(j = i; 1; j = k) {
          k = j+shift;
          if(k >= n) k -= n;
          if(k == i) break;
          memcpy(ptr+es*j, ptr+es*k, es); // arr[j] = arr[k];
        }
        memcpy(ptr+es*j, tmp, es); // arr[j] = tmp;
      }
    }
    
    // cycle right shifts away from zero
    void array_cycle_right(void *_ptr, size_t n, size_t es, size_t shift)
    {
      if(!n || !shift) return; // cannot mod by zero
      shift = shift % n; // shift cannot be greater than n
      // cycle right by `s` is equivalent to cycle left by `n - s`
      array_cycle_left(_ptr, n, es, n - shift);
    }
    
    // Get Greatest Common Divisor using binary GCD algorithm
    // http://en.wikipedia.org/wiki/Binary_GCD_algorithm
    unsigned int calc_GCD(unsigned int a, unsigned int b)
    {
      unsigned int shift, tmp;
    
      if(a == 0) return b;
      if(b == 0) return a;
    
      // Find power of two divisor
      for(shift = 0; ((a | b) & 1) == 0; shift++) { a >>= 1; b >>= 1; }
    
      // Remove remaining factors of two from a - they are not common
      while((a & 1) == 0) a >>= 1;
    
      do
      {
        // Remove remaining factors of two from b - they are not common
        while((b & 1) == 0) b >>= 1;
    
        if(a > b) { tmp = a; a = b; b = tmp; } // swap a,b
        b = b - a;
      }
      while(b != 0);
    
      return a << shift;
    }
    

    Edit: This algorithm may also have better performance vs array reversal (when N is large and M is small) due to cache locality, since we are looping over the array in small steps.

    Final note: if your array is small, triple reverse is simple. If you have a large array, it is worth the overhead of working out the GCD to reduce the number of moves by a factor of 2. Ref: http://www.geeksforgeeks.org/array-rotation/

    0 讨论(0)
  • 2020-11-28 05:54

    It's just a matter of representation. Keep the current index as an integer variable and when traversing the array use modulo operator to know when to wrap around. Shifting is then only changing the value of the current index, wrapping it around the size of the array. This is of course O(1).

    For example:

    int index = 0;
    Array a = new Array[SIZE];
    
    get_next_element() {
        index = (index + 1) % SIZE; 
        return a[index];
    }
    
    shift(int how_many) {
        index = (index+how_many) % SIZE;
    }
    
    0 讨论(0)
  • 2020-11-28 05:54
    static int [] shift(int arr[], int index, int k, int rem)
    {
        if(k <= 0 || arr == null || arr.length == 0 || rem == 0 || index >= arr.length)
        {
            return arr;
        }
    
        int temp = arr[index];
    
        arr = shift(arr, (index+k) % arr.length, k, rem - 1);
    
        arr[(index+k) % arr.length] = temp;
    
        return arr;
    }
    
    0 讨论(0)
提交回复
热议问题