Самый быстрый алгоритм для сдвига окружности массива N размера для позиции М - PullRequest
31 голосов
/ 18 мая 2009

Какой самый быстрый алгоритм для массива сдвига окружности для M позиций?
Например, [3 4 5 2 3 1 4] смещение M = 2 позиции должно быть [1 4 3 4 5 2 3].

Большое спасибо.

Ответы [ 23 ]

50 голосов
/ 18 мая 2009

Если вы хотите использовать время O (n) и не использовать лишнюю память (поскольку массив был указан), используйте алгоритм из книги Джона Бентли «Программирование жемчужин, 2-е издание». Это меняет местами все элементы дважды. Не так быстро, как использование связанных списков, но использует меньше памяти и концептуально прост.

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 включительно.

22 голосов
/ 18 мая 2009

Это просто вопрос представительства. Сохраняйте текущий индекс как целочисленную переменную, а при обходе массива используйте оператор по модулю, чтобы знать, когда обернуться. Сдвиг тогда только изменяет значение текущего индекса, оборачивая его вокруг размера массива. Это конечно 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;
}
14 голосов
/ 21 сентября 2015

Оптимальное решение

Вопрос задан быстрее всех. Реверсировать три раза проще, но каждый элемент перемещается ровно дважды, занимает 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=9, M=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;

Обратите внимание, что каждый элемент читается один раз и записывается один раз.

Диаграмма смещения N=9, M=3

Diagram of cycle shift

Первый цикл показан черным цветом с цифрами, указывающими порядок операций. Второй и третий циклы показаны серым цветом.

Требуемое количество циклов: Величайший общий делитель (GCD) N и M. Если GCD равен 3, мы начинаем цикл с каждого из {0,1,2}. Расчет GCD выполняется быстро с помощью алгоритма двоичного 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, чтобы уменьшить количество ходов в 2 раза. Ссылка: http://www.geeksforgeeks.org/array-rotation/

7 голосов
/ 18 мая 2009

Установите его с помощью указателей, и это почти не займет времени. Каждый элемент указывает на следующий, а «последний» (нет последнего; в конце концов, вы сказали, что он был круглым) указывает на первый. Один указатель на «начало» (первый элемент) и, возможно, длину, и у вас есть свой массив. Теперь, чтобы сдвинуться, просто проведите указатель начала по кругу.

Попросите хороший алгоритм, и вы получите разумные идеи. Спросите быстрее , и вы получите странные идеи!

4 голосов
/ 06 ноября 2014

Этот алгоритм работает в 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;
}
3 голосов
/ 15 октября 2012
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

2 голосов
/ 28 августа 2018

Очень простое решение. Это очень быстрый способ, здесь я использую временный массив того же размера или оригинала и присоединяю к исходной переменной в конце. Этот метод использует 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;
2 голосов
/ 16 июля 2012

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];
        }
    }
}
1 голос
/ 10 марта 2017

Вот простая и эффективная общая функция поворота на месте в C ++, менее 10 строк.

, который взят из моего ответа на другой вопрос. Как вращать массив?

#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';
}
1 голос
/ 18 мая 2009

В зависимости от используемой структуры данных, вы можете сделать это в O (1). Я думаю, что самый быстрый способ - это хранить массив в форме связанного списка и иметь хеш-таблицу, которая может переводить между «индексом» в массиве и «указателем» на запись. Таким образом, вы можете найти соответствующие головы и хвосты в O (1) и выполнить переподключение в O (1) (и обновить хэш-таблицу после переключения в O (1)). Это, конечно, было бы очень «грязным» решением, но если все, что вас интересует, это скорость сдвига, то это будет сделано (за счет более длинной вставки и поиска в массиве, но все равно останется O 1))

Если у вас есть данные в чистом массиве, я не думаю, что вы можете избежать O (n).

Кодирование зависит от того, какой язык вы используете.

В Python, например, вы можете его «нарезать» (предположим, что n - это размер смещения):

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

(Я знаю, что поиск хеша в теории не O (1), но мы здесь практичны, а не теоретичны, по крайней мере, я на это надеюсь ...)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...