Мне было интересно узнать о временной сложности некоторых встроенных методов Ruby, в частности этих двух. Я думаю, что лучшее, что я смог придумать для метода перестановки самостоятельно, - это Θ (n · n!), Работает ли Ruby? Если да, пожалуйста, помогите мне понять их алгоритм.
Какова временная сложность Ruby, встроенная в методы #permutation и #repeated_permutation?
Ответ 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
.
Ответ 2
Существуют алгоритмы для итеративного создания всех перестановок списка.
Как? Алгоритм генерирует все перестановки [1,2,3,4,...,n]
в лексикографическом порядке. При одной перестановке алгоритм генерирует следующую лексикографическую перестановку в O(1)
времени.
Это шаги
- Найти наибольший индекс
k
такой, чтоa[k] < a[k + 1]
, если такой индекс не существует, перестановка является последней перестановкой - Найти наибольший индекс
l
большеk
, чтобыa[k] < a[l]
- Поменяйте значение
a[k]
на значениеa[l]
- Отмените последовательность от
a[k + 1]
до конечного элемента и включите егоa[n]
В чем сложность?
Каждый шаг O(1)
и существуют перестановки O(n!)
, поэтому общая сложность O(n!)
.