для генерации случайной перестановки массива за время O (n) и пространство O (1)

Нам нужно сгенерировать массив {1,2,3,..,n} в пространстве O(1).
Я могу сделать это в пространстве O(n).

Я сделал O(n) космическое решение, сначала сохранив массив, а затем рандомизировав его на месте. Но как это сделать без хранения массива в O(1) пространстве.

Я просто генерирую случайное число, и вместо того, чтобы хранить их, мне нужно распечатать их, так как для хранения потребуется пространство O (n), но мне нужно сделать это в пространстве O (1), и в чем я сомневаюсь, если мы продолжим генерировать случайное число и выведите их, могут быть некоторые числа от 1 до n, которые могут быть сгенерированы более одного раза, а некоторые могут не сгенерироваться. Итак, как мне напечатать все числа ровно один раз в пространстве O (1)?

P.S.-Мне не дается никакого массива. Ввод просто «n», и мне нужно напечатать перестановку массива {1,2,3,...,n} за время O (n) и пространство O (1).


person Abhishek Yadav    schedule 24.08.2015    source источник
comment
Доказуемо невозможно в довольно естественной модели.   -  person David Eisenstat    schedule 24.08.2015
comment
Если вам дан массив и вам нужно его перемешать, это можно сделать с помощью fisher- Йейтс перемешивает. Сгенерировать массив с нуля без дополнительного места невозможно по определению, так как сам вывод O(n), и его нужно сгенерировать.   -  person amit    schedule 24.08.2015
comment
Требование к пространству O(1), безусловно, относится к дополнительному хранилищу. Не существует решения с пространственной сложностью O (1), которое дает = полиномиальный результат (учитывая пространство вывода).   -  person Rubens    schedule 24.08.2015
comment
Если вы используете С++, вы можете использовать функцию random_shuffle() или next_permutation(), если вам нужно.   -  person vish4071    schedule 24.08.2015
comment
Я подозреваю, что вы неправильно поняли задание. Есть разница между O(1) пространством и O(1) дополнительным пространством.   -  person pjs    schedule 24.08.2015
comment
Мне неясно, генерируете ли вы случайные элементы и сохраняете их в массиве или перетасовываете существующий массив. Что он?   -  person Erick G. Hagstrom    schedule 24.08.2015
comment
@DavidEisenstat Итак, Фишер-Йейтс не работает, потому что период PRNG позволяет достичь только части перестановок?   -  person Niklas B.    schedule 24.08.2015
comment
@НикласБ. Не получится, потому что, как говорит обновление от Абхишека, массива нет. Вероятно, если бы перестановки, которые неотличимы от истинной случайности только с точки зрения вычислений, были в порядке (делая стандартные криптографические предположения), тогда могло бы быть какое-то решение, включающее криптопримитивы.   -  person David Eisenstat    schedule 24.08.2015
comment
@DavidEisenstat Я не понял, что ты сказал. Пожалуйста, можете уточнить. Существует ли какое-то решение? Или это оказывается проблемой, решение которой нам еще предстоит найти.   -  person Abhishek Yadav    schedule 24.08.2015
comment
Ой! Таким образом, OP действительно означает пространство O (1). Например, сгенерируйте один элемент массива, распечатайте его и отбросьте. Сгенерировать другой элемент и т. д. Так что на самом деле никакого массива нет, это просто случайная перестановка последовательности 1, 2, 3,..., n. Случайные розыгрыши, без замены, за время O(n) и пространство O(1).   -  person Erick G. Hagstrom    schedule 24.08.2015
comment
Итак, не ответ, а ЕСЛИ был PRNG такой, что а) вы можете указать точный период и б) вы можете сопоставить вывод один к одному с целыми числами 1..n, тогда вы золотой . Просто выполните getNext() n раз. Я не надеюсь на существование такого существа. @DavidEisenstat, ты профессионал. Так ли безнадежен поиск, как кажется?   -  person Erick G. Hagstrom    schedule 24.08.2015
comment
Упс, @amit, я не имел в виду никакого пренебрежения. Вас, очевидно, здесь тоже уважают.   -  person Erick G. Hagstrom    schedule 24.08.2015
comment
@ErickG.Hagstrom Если бы у вас была псевдослучайная перестановка в наборе размера Theta (n), то это помогло бы. Я не крипто-эксперт, поэтому я не знаю, что конкретно подойдет для этого счета.   -  person David Eisenstat    schedule 24.08.2015
comment
Возможным вариантом был бы регистр сдвига с линейной обратной связью (или, возможно, генератор случайных чисел xorshift) с периодом n. Хотя я не знаю, как построить такой, который бы работал, если n не является степенью двойки.   -  person Jim Mischel    schedule 24.08.2015
comment
Умная идея. Придирка: k-битный LFSR с максимальной длиной цикла будет генерировать последовательность из n=2**k-1 значений, поскольку он не может генерировать 0.   -  person njuffa    schedule 24.08.2015
comment
Симметричный шифр на самом деле является PRNG с отображением один к одному. Если вы создадите его правильно, вы можете случайно повторять через последовательность целых чисел, от 1 до n. Ответ mcdowella изложил ту же идею: Format-Preserving-Encryption, Feistel Cipher, AES-FFX, режим счетчика.   -  person Thomas B.    schedule 26.08.2015


Ответы (4)


Я создал генератор регистров сдвига с линейной обратной связью, который, как мне кажется, соответствует вашим требованиям. Реализация основана на LFSR Фибоначчи, поэтому она обеспечивает полный цикл для заданного количества битов. Я пошел дальше и ввел полиномиальные коэффициенты до 19 бит и выбрал соответствующий набор коэффициентов на основе указанного значения N. Сгенерированные значения, превышающие N, выбрасываются за борт, но общее количество значений в полном цикле меньше 2N, поэтому ваши N значения будут получены за O(N) времени. LFSR сохраняет одно слово состояния, поэтому это пробел O(1).

Вот реализация на Ruby:

#!/usr/bin/env ruby -w

# Linear Feedback Shift Register generator which operates on smallest bit
# range possible for a specified integer range, and skips over values outside
# the specified range. Since this attains full cycle length for the specified
# number of bits, and the number of bits is minimized relative to the specified
# N, the total number of iterations is bounded by 2N and is therefore O(N).
class LFSR
  # Polynomials for maximal LFSRs determine shift amounts for up to 19 bits.
  # See https://en.wikipedia.org/wiki/Linear_feedback_shift_register for
  # details. Add more entries if you need more than 19 bits.
  SHIFT_AMT = [
    [], [], [1], [1], [1], [2], [1], [1], [2, 3, 4], [4], [3], [2],
    [1, 2, 8], [1, 2, 5], [1, 2, 12], [1], [2, 3, 5], [3], [7], [1, 2, 5]
  ]

  # Constructor for the LFSR.  Specify the N and seed value.
  def initialize(n, seed)
    @n = n
    @state =  (seed % n) + 1
    @num_bits = Math.log2(n).floor + 1
  end

  # Generate the next iterate of the LFSR.  If it's above the specified N,
  # keep trying until you're done.
  def next_value
    loop do
      bit = @state
      SHIFT_AMT[@num_bits].each { |amt| bit ^= (@state >> amt) }
      @state = ((@state >> 1) | ((bit & 1) << (@num_bits - 1)))
      return @state if @state <= @n
    end
  end
end

N = (ARGV.shift || 100).to_i # Specify the 'N' value on cmd line. Default = 100
SEED = (ARGV.shift || 0x7FFF).to_i # Optionally specify a "seed" for the LFSR
my_lfsr = LFSR.new(N, SEED)  # Instantiate an LFSR object
N.times { p my_lfsr.next_value }   # Invoke it N times, print the results
person pjs    schedule 25.08.2015
comment
Насколько зависит от n объем пространства, необходимый для массива SHIFT_AMT? - person גלעד ברקן; 25.08.2015
comment
@גלעדברקן Как видите, вряд ли. Предоставляемые наборы SHIFT_AMT содержат до 19 бит, а самый большой подмассив состоит из 3 элементов. Насколько я понимаю, если мы ограничим N 32- или 64-битными целыми числами, это все равно будет таблица фиксированного размера, в ней будет всего 45 дополнительных записей для перехода на 64-битные. - person pjs; 25.08.2015
comment
Насколько зависит от n среднее количество итераций, необходимых для next_value, чтобы найти итерацию в диапазоне? Что это в среднем? - person גלעד ברקן; 25.08.2015
comment
@גלעדברקן Вы перечислите полный цикл всех целых чисел от 1 до наименьшей степени 2, содержащих N, что меньше 2N и явно содержит все значения от 1 до N. Следовательно, количество итераций в среднем меньше двух на вызов next_value. Это основа моего утверждения, что O(N) генерировать их все, поскольку оно ограничено 2N. - person pjs; 25.08.2015
comment
О, да, вы упомянули об этом, звучит интересно, о чем нужно узнать больше. - person גלעד ברקן; 25.08.2015

Если n равно степени 2, блочный шифр с размером блока n бит может использоваться для генерации перестановки n элементов — просто запишите Encrypt(0), Encrypt(1)... Encrypt(n-1) .

Если n не является степенью числа 2, пусть m будет первой степенью числа 2 выше n. Зашифруйте 0..n-1, и если результат >= n, зашифруйте его снова, пока не получите значение в пределах диапазона. Это равносильно записи перестановки m элементов в виде цикла, а затем удалению элементов >= n.

Если у вас нет стандартного блочного шифра требуемого размера, вы можете использовать конструкцию Luby-Rackoff, также известную как Feistel, чтобы создать его, используя хеш-функцию в качестве F-операции в https://en.wikipedia.org/wiki/Feistel_cipher. Особенность сетей Фейстеля, в которых F() производит более одного бита, рассматриваемого как перестановки, заключается в том, что они никогда не производят нечетных перестановок: если выход Фейстеля имеет ширину k бит, каждый раунд дает число, кратное 2^(k-1) 2- циклов, что дает четную перестановку для k > 1, поэтому вы можете немного подумать об этом и/или использовать несколько раундов Фейстеля с разными стилями обратной связи, чтобы получить из этого достаточно случайные перестановки. Надлежащим образом разработанную систему раундов Фистеля с 1 битом выходных данных Фейстеля можно рассматривать как неявную конструкцию сети обмена, которую можно использовать для реализации произвольных перестановок в сетях.

person mcdowella    schedule 24.08.2015

Строго говоря, решение O(1) невозможно, потому что само число n занимает для хранения log(n) бит.

С другой стороны, если цель упражнения состоит в том, чтобы избежать массива из n целых чисел, и вы готовы пожертвовать некоторой строгостью, а именно предположить, что n! может быть представлено в O(1) памяти, решение состоит в том, чтобы сгенерировать случайное число k в диапазоне [0, n!) и вычислить k-я перестановка, печатающая числа по мере их вычисления.

person user58697    schedule 24.08.2015
comment
Интересная идея. n лучше оставаться маленьким, а? - person Erick G. Hagstrom; 25.08.2015
comment
@ErickG.Hagstrom Готов поспорить - person user58697; 25.08.2015

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

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

Два основных способа идеального смешивания списка (при наличии идеального генератора случайных чисел):

for each element in the list:
  select a random element later in the list.
  swap.

for each element in the list:
  select a random element earlier in the list.
  swap.

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

person warren    schedule 24.08.2015