Ответ 1
Перестановка
Array#permutation
возвращает Enumerator с массивами n!
, поэтому временная сложность будет не менее O(n!)
.
Я написал этот метод:
def slow_method(n)
(1..n).to_a.permutation.each do |p|
p
end
end
Он ничего не делает с p
, ожидая принудительного генерации всех перестановок. Построение массива всех перестановок будет использовать слишком много памяти.
Этот метод был вызван 10 раз для n между 10 и 13, а среднее время в секундах:
t10 = 0.618895
t11 = 6.7815425
t12 = 82.896605
t13 = 1073.015602
O(n!)
выглядит как разумное приближение:
t13/fact(13)*fact(12)/t12 #=> 0.995694114280165
t13/fact(13)*fact(11)/t11 #=> 1.0142685297667369
t13/fact(13)*fact(10)/t10 #=> 1.0103498450722133
O(n*n!)
не:
t13/(fact(13)*13)*fact(12)*12/t12 #=> 0.9191022593355369
t13/(fact(13)*13)*fact(11)*11/t11 #=> 0.8582272174949312
t13/(fact(13)*13)*fact(10)*10/t10 #=> 0.777192188517087
Похоже, что генерация O(n!)
, но выполнение чего-либо с созданными массивами приведет к общей сложности O(n*n!)
.
Почему не поколение O(n*n!)
? Это может исходить из того, что при рекурсивном генерации [1,2,3,4,5].permutation
остальные перестановки одинаковы для [1,2]
и [2,1]
.
O(n!)
уже настолько медленен, что n
никогда не будет намного больше 10, поэтому O(n*n!)
не намного хуже. Для n=20
, n!
есть 2432902008176640000
и n*n!
is 48658040163532800000
.
Повторная перестановка
[1,2,...n].repeated_permutation(k)
генерирует n**k
Массивы из k элементов.
Сложность должна быть либо O(n**k)
, либо O(k*n**k)
.
Для k=n
он становится O(n**n)
или O(n**(n+1))
, что даже (намного) хуже, чем для permutation
.