Подтвердить что ты не робот

Можно ли инвертировать массив с постоянным дополнительным пространством?

Скажем, у меня есть массив A с n уникальными элементами на диапазоне [0, n). Другими словами, у меня есть перестановка целых чисел [0, n).

Возможно преобразование A в B с использованием O (1) дополнительного пространства (AKA на месте), так что B [A [i]] = i?

Например:

       A                  B
[3, 1, 0, 2, 4] -> [2, 1, 3, 0, 4]
4b9b3361

Ответ 1

Да, возможно, с O (n ^ 2) алгоритмом времени:

Возьмите элемент с индексом 0, а затем напишите 0 в ячейку, проиндексированную этим элементом. Затем используйте только перезаписанный элемент, чтобы получить следующий индекс и записать там прежний индекс. Продолжайте, пока не вернетесь к индексу 0. Это алгоритм лидера цикла.

Затем сделайте то же самое, начиная с индекса 1, 2,... Но прежде чем делать какие-либо изменения, выполняйте алгоритм лидера цикла без каких-либо изменений, начиная с этого индекса. Если этот цикл содержит индекс ниже начального индекса, просто пропустите его.


Или этот O (n ^ 3) алгоритм времени:

Возьмите элемент с индексом 0, а затем напишите 0 в ячейку, проиндексированную этим элементом. Затем используйте только перезаписанный элемент, чтобы получить следующий индекс и записать там прежний индекс. Продолжайте, пока не вернетесь к индексу 0.

Затем сделайте то же самое, начиная с индекса 1, 2,... Но прежде чем делать какие-либо изменения, выполните алгоритм лидера цикла без каких-либо изменений, начиная со всех предыдущих индексов. Если текущий индекс присутствует в любом предыдущем цикле, просто пропустите его.


Я написал (немного оптимизирован) реализация алгоритма O (n ^ 2) в С++ 11, чтобы определить, сколько дополнительных запросов требуется для каждого элемента на если случайная перестановка инвертирована. Вот результаты:

size accesses
2^10 2.76172
2^12 4.77271
2^14 6.36212
2^16 7.10641
2^18 9.05811
2^20 10.3053
2^22 11.6851
2^24 12.6975
2^26 14.6125
2^28 16.0617

Пока размер растет экспоненциально, количество обращений к элементу растет почти линейно, поэтому ожидаемая временная сложность для случайных перестановок - это что-то вроде O (n log n).

Ответ 2

Инвертирование массива A требует от нас найти перестановку B, которая удовлетворяет требованию A[B[i]] == i для всех i.

Чтобы построить обратное на месте, мы должны поменять элементы и индексы, установив A[A[i]] = i для каждого элемента A[i]. Очевидно, что если бы мы просто перешли через A и выполнили вышеупомянутую замену, мы могли бы переопределить предстоящие элементы в A, и наши вычисления потерпят неудачу.

Поэтому мы должны поменять элементы и индексы на циклы A, следуя c = A[c], пока не достигнем нашего начального индекса цикла c = i.

Каждый элемент из A принадлежит одному из таких циклов. Поскольку у нас нет места для хранения, если элемент A[i] уже обработан и должен быть пропущен, мы должны следовать его циклу: если мы достигнем индекса c < i, мы бы знали, что этот элемент является частью ранее обработанный цикл.

Этот алгоритм имеет худшую временную сложность O (n²), среднюю сложность во времени O (n log n) и лучшую Сложная временная сложность O (n).

function invert(array) {
  main:
  for (var i = 0, length = array.length; i < length; ++i) {
    
    // check if this cycle has already been traversed before:
    for (var c = array[i]; c != i; c = array[c]) {
      if (c <= i) continue main;
    }
    
    // Replacing each cycle element with its predecessors index:
    var c_index = i, 
        c = array[i];
    do {
      var tmp = array[c];
      array[c] = c_index; // replace
      c_index = c; // move forward
      c = tmp;
    } while (i != c_index)
      
  }
  return array;
}
  
console.log(invert([3, 1, 0, 2, 4])); // [2, 1, 3, 0, 4]