用于M位置的圆移N大小数组的最快算法

时间:2009-05-18 04:55:18

标签: algorithm arrays math puzzle programming-pearls

M位置的圆移位阵列的最快算法是什么?
例如,[3 4 5 2 3 1 4]移位M = 2个位置应为[1 4 3 4 5 2 3]

非常感谢。

24 个答案:

答案 0 :(得分:48)

如果您想要O(n)时间并且没有额外的内存使用(因为指定了数组),请使用Jon Bentley的书“Programming Pearls 2nd Edition”中的算法。它将所有元素交换两次。没有使用链接列表那么快但使用更少的内存并且在概念上很简单。

shiftArray( theArray, M ):
    size = len( theArray )
    assert( size > M )
    reverseArray( theArray, 0, size - 1 )
    reverseArray( theArray, 0, M - 1 )
    reverseArray( theArray, M, size - 1 )

reverseArray(anArray,startIndex,endIndex)将从startIndex到endIndex的元素顺序颠倒过来。

答案 1 :(得分:22)

这只是一个代表问题。将当前索引保持为整数变量,并且在遍历数组时使用模运算符来知道何时换行。然后,移位仅改变当前索引的值,将其包围在数组的大小周围。这当然是O(1)。

例如:

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;
}

答案 2 :(得分:14)

最佳解决方案

问题要求最快。反转三次是最简单的,但每个元素移动两次,需要O(N)时间和O(1)空间。在O(N)时间和O(1)空间中,也可以循环移动每个元素一次的数组。

我们可以将N=9长度M=1的数组循环移位一个周期:

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

如果N=9M=3我们可以循环移位三个周期:

  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;
  4. 注意每个元素只读一次并写一次。

    转移N=9, M=3

    的图表

    Diagram of cycle shift

    第一个循环以黑色显示,数字表示操作顺序。第二个和第三个循环以灰色显示。

    所需的周期数是NM的{​​{3}}(GCD)。如果GCD为3,我们在每个{0,1,2}开始一个循环。使用Greatest Common Divisor计算GCD的速度很快。

    示例代码:

    // 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;
      }
    }
    

    任何数组类型的C代码:

    // 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;
    }
    

    编辑:由于缓存局部性,此算法也可能比阵列反转(当N很大且M很小时)具有更好的性能,因为我们正在循环数组小步。

    最后注意事项:如果你的阵列很小,三重反转很简单。如果你有一个大阵列,那么制定GCD以减少移动次数是值得的。 参考:binary GCD algorithm

答案 3 :(得分:7)

用指针设置它,几乎没有时间。每个元素指向下一个,而“最后一个”(没有最后一个;毕竟,你说它是圆形的)指向第一个。一个指向“开始”(第一个元素)的指针,也许是一个长度,你有你的数组。现在,为了完成你的转变,你只需沿着圆圈走开始指针。

要求一个好的算法,你会得到明智的想法。要求最快,你会得到奇怪的想法!

答案 4 :(得分:4)

该算法在O(n)时间和O(1)空间中运行。 我们的想法是跟踪班次中的每个循环组(由nextGroup变量编号)。

var shiftLeft = function(list, m) {
    var from = 0;
    var val = list[from];
    var nextGroup = 1;
    for(var i = 0; i < list.length; i++) {
        var to = ((from - m) + list.length) % list.length;
        if(to == from)
            break;

        var temp = list[to];
        list[to] = val;
        from = to;
        val = temp;

        if(from < nextGroup) {
            from = nextGroup++;
            val = list[from];
        }
    }
    return list;
}

答案 5 :(得分:3)

def shift(nelements, k):       
    result = []
    length = len(nelements)
    start = (length - k) % length
    for i in range(length):
        result.append(nelements[(start + i) % length])
    return result

此代码即使在负移位k上也能正常工作

答案 6 :(得分:2)

一个非常简单的解决方案。这是一种非常快的方法,在这里我使用具有相同大小或原始大小的临时数组,并在最后附加到原始变量。 该方法使用O(n)时间复杂度和O(n)空间复杂度,实现起来非常简单。

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;

答案 7 :(得分:2)

C arrayShiftRight函数。如果shift为负,则函数向左移动数组。 它针对较少的内存使用进行了优化。运行时间为O(n)。

void arrayShiftRight(int array[], int size, int shift) {
    int len;

    //cut extra shift
    shift %= size;

    //if shift is less then 0 - redirect shifting left
    if ( shift < 0 ) {
        shift += size;
    }

    len = size - shift;

    //choosing the algorithm which needs less memory
    if ( shift < len ) {
        //creating temporary array
        int tmpArray[shift];

        //filling tmp array
        for ( int i = 0, j = len; i < shift; i++, j++ ) {
            tmpArray[i] = array[j];
        }

        //shifting array
        for ( int i = size - 1, j = i - shift; j >= 0; i--, j-- ) {
            array[i] = array[j];
        }

        //inserting lost values from tmp array
        for ( int i = 0; i < shift; i++ ) {
            array[i] = tmpArray[i];
        }
    } else {
        //creating temporary array
        int tmpArray[len];

        //filling tmp array
        for ( int i = 0; i < len; i++ ) {
            tmpArray[i] = array[i];
        }

        //shifting array
        for ( int i = 0, j = len; j < size; i++, j++ ) {
            array[i] = array[j];
        }

        //inserting lost values from tmp array
        for ( int i = shift, j = 0; i < size; i++, j++ ) {
            array[i] = tmpArray[j];
        }
    }
}

答案 8 :(得分:1)

根据您使用的数据结构,您可以在O(1)中执行此操作。我认为最快的方法是以链表的形式保存数组,并且有一个哈希表,可以在数组中的“index”和条目的“指针”之间进行转换。通过这种方式,您可以在O(1)中找到相关的头部和尾部,并在O(1)中重新连接(并在O(1)中切换后更新哈希表)。这当然是一个非常“混乱”的解决方案,但如果你感兴趣的只是转换的速度,那就行了(以数组中更长的插入和查找为代价,但它仍将保持为O( 1))

如果您拥有纯数组中的数据,我认为您不能避免使用O(n)。

编码方式,它取决于您使用的语言。

例如,在Python中,您可以“切片”它(假设n是移位大小):

result = original[-n:]+original[:-n]

(我知道哈希查找在理论上不是O(1)但我们在这里是实用的而不是理论上的,至少我希望如此......)

答案 9 :(得分:1)

这应该可以循环移动数组: 输入:{1,2,3,5,6,7,8}; for循环后数组中的输出值:{8,7,1,2,3,5,6,8,7}

 class Program
    {
        static void Main(string[] args)
        {
            int[] array = { 1, 2, 3, 5, 6, 7, 8 };
            int index = 2;
            int[] tempArray = new int[array.Length];
            array.CopyTo(tempArray, 0);

            for (int i = 0; i < array.Length - index; i++)
            {
                array[index + i] = tempArray[i];
            }

            for (int i = 0; i < index; i++)
            {
                array[i] = tempArray[array.Length -1 - i];
            }            
        }
    }

答案 10 :(得分:1)

这是一个简单而有效的C ++旋转函数,少于10行。

摘自我在另一个问题上的回答。 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';
}

答案 11 :(得分:0)

@IsaacTurner的答案(C) https://stackoverflow.com/a/32698823/4386969

和@SomeStrangeUser的答案(Java): https://stackoverflow.com/a/18154984/4386969

提供一种简单的O(N)时间,O(1)空间算法,该算法可以回答问题,并且需要精确的N个元素分配。我相信(如果我错了,有人会纠正我的话)认为没有必要计算N和M之间的gcd。仅计算我们放置在正确位置的元素数量就足够了。这是因为一旦将元素放置在正确的位置,就可以保证在当前循环和后续循环中都不必再次访问它。

这是一个Python 3实现,具有以下简化功能:

# circle shift an array to the left by M 
def arrayCircleLeftShift(a, M):
    N = len(a)
    numAccessed = 0
    cycleIdx = 0
    while numAccessed != N:
        idx = cycleIdx
        swapIdx = (idx + M) % N
        tmp = a[idx]
        while swapIdx != cycleIdx:
            a[idx] = a[swapIdx]
            numAccessed += 1
            idx = swapIdx
            swapIdx = (idx + M) % N
        a[idx] = tmp
        numAccessed += 1
        cycleIdx += 1

答案 12 :(得分:0)

Swift 4版本,用于向左移动数组。

func rotLeft(a: [Int], d: Int) -> [Int] {

   var result = a
   func reverse(start: Int, end: Int) {
      var start = start
      var end = end
      while start < end {
         result.swapAt(start, end)
         start += 1
         end -= 1
      }
   }

   let lenght = a.count
   reverse(start: 0, end: lenght - 1)
   reverse(start: lenght - d, end: lenght - 1)
   reverse(start: 0, end: lenght - d - 1)
   return result
}

例如,如果输入数组为a = [1, 2, 3, 4, 5],而左移偏移量为d = 4,则结果将为[5, 1, 2, 3, 4]

答案 13 :(得分:0)

这是我的Java解决方案,在Codility上我获得了100%的任务分数和100%的正确性:

class Solution {
    public int[] solution(int[] A, int K) {
        // write your code in Java SE 8
        if (A.length > 0)
        {
            int[] arr = new int[A.length];
            if (K > A.length)
                K = K % A.length;

            for (int i=0; i<A.length-K; i++)
                arr[i+K] = A[i];

            for (int j=A.length-K; j<A.length; j++)
                arr[j-(A.length-K)] = A[j];

            return arr;
        }
        else
            return new int[0];
    }
}

请注意,尽管看到了两个for循环,但整个数组上的迭代仅执行了一次。

答案 14 :(得分:0)

类似于@IsaacTurner,由于不必要的复制而不够优雅,但实现起来很短。

这个想法-将索引0上的元素A与位于A的目标位置的元素B交换。现在B是第一个。将其与位于目标B上的元素C交换。继续直到目标位置不为0。

如果最大公约数不是1,那么您还没有完成-您需要继续交换,但是现在在起点和终点使用索引1。

继续,直到您的起始位置不是gcd。

function sortByFields(objs, fields) {
  const diffToFields = obj =>
    _(fields).map(f => Math.abs(obj[f] - f.number)).sum();
  return _(objs).sortBy(diffToFields).value();
}

const sortedData = sortByFields(data, [
  {field: 'score', number: 21},
  {field: 'average', number: 50},
]);

答案 15 :(得分:0)

此方法可以完成这项工作:

public static int[] solution1(int[] A, int K) {
    int temp[] = new int[A.length];

    int count = 0;

    int orignalItration = (K < A.length) ? K :(K%A.length); 


    for (int i = orignalItration; i < A.length; i++) {
        temp[i] = A[count++];
    }
    for (int i = 0; i < orignalItration; i++) {
        temp[i] = A[count++];
    }

    return temp;
}

答案 16 :(得分:0)

我的一个朋友在开玩笑时问我如何换阵,我想出了这个解决方案(见ideone链接),现在我见过你的,有人看起来有点深奥。

看看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;
}

答案 17 :(得分:0)

这是另一个(C ++):

void shift_vec(vector<int>& v, size_t a)
{
    size_t max_s = v.size() / a;
    for( size_t s = 1; s < max_s; ++s )
        for( size_t i = 0; i < a; ++i )
            swap( v[i], v[s*a+i] );
    for( size_t i = 0; i < a; ++i )
        swap( v[i], v[(max_s*a+i) % v.size()] );
}

当然,它并不像着名的反三次解决方案那么优雅,但取决于机器,它可以是similary fast

答案 18 :(得分:0)

理论上,最快的是这样的循环:

if (begin != middle && middle != end)
{
    for (i = middle; ; )
    {
        swap(arr[begin++], arr[i++]);
        if (begin == middle && i == end) { break; }
        if (begin == middle) { middle = i; }
        else if (i == end) { i = middle; }
    }
}

在实践中,您应该对其进行分析并查看。

答案 19 :(得分:0)

Ruby示例:

def move_cyclic2 array, move_cnt
  move_cnt = array.length - move_cnt % array.length 
  if !(move_cnt == 0 || move_cnt == array.length)            
    array.replace( array[move_cnt..-1] + array[0...move_cnt] )  
  end   
end

答案 20 :(得分:0)

如果您对Java实现感兴趣,请参阅此内容:

Programming Pearls: Circular Left/Right Shift Operation

答案 21 :(得分:0)

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;
}

答案 22 :(得分:0)

circleArray有一些错误,并不是在所有情况下都有效!

循环必须继续while i1 < i2 NOT i1 < last - 1

void Shift(int* _array, int _size, int _moves)
{
    _moves = _size - _moves;
    int i2 = _moves;
         int i1 = -1;
         while(++i1 < i2)
    {
        int tmp = _array[i2];
        _array[i2] = _array[i1];
        _array[i1] = tmp;
        if(++i2 == _size) i2 = _moves;
    }
}

答案 23 :(得分:0)

将两个索引保留在数组中,一个索引从数组的开头到数组的结尾。另一个索引从最后一个Mth位置开始,并且循环遍历最后M个元素任意次。始终取O(n)。无需额外空间。

circleArray(Elements,M){
 int size=size-of(Elements);

 //first index
 int i1=0;

 assert(size>M)

 //second index starting from mth position from the last
 int i2=size-M;

 //until first index reaches the end
 while(i1<size-1){

  //swap the elements of the array pointed by both indexes
  swap(i1,i2,Elements);

  //increment first pointer by 1
  i1++;

  //increment second pointer. if it goes out of array, come back to
  //mth position from the last
  if(++i2==size) i2=size-M;

 }
}