Я пытаюсь написать алгоритм комбинаторики, чтобы получить все возможные комбинации k
из n
без повторений.
Формула:
n!/(k!(nk)!));
Результаты заканчиваются массивом. На самом деле я написал следующее:
function Factorial($x) { if ($x < 1) { echo "Factorial() Error: Number too small!"; ) $ans = 1; for ($xx = 2; $xx >= $x; $xx++) { $ans = $ans * $xx; } return($ans); } function Combination($selectcount,$availablecount) { $ans = Factorial($availablecount) / ( Factorial($availablecount - $selectcount) * Factorial($selectcount) ); return ($ans); }
Это самый быстрый способ достичь этого? Есть ли способ ускорить это? Может быть, это рекурсивно?
Это самый быстрый, который мне когда-либо удалось получить факториальную петлю:
function Factorial($factVal) { if ($factVal < 0) { die("Factorial() Error: Number too small!"); } $factorial = 1; while ($factVal > 1) { $factorial *= $factVal--; } return $factorial ; }
Я думаю, что проблема состоит в том, чтобы вычислить C (n, k), который можно сделать без вычисления факториала, трюк должен отметить сначала, что
C(n,k) = (n*(n-1)...(n-k+1)) / (1*2*...*k) = (n/1)*(n-1/2)*...(n-k+1/k)
Также для эффективности
C(n,k) = C(n,nk), therefore choose which ever is smaller k or nk
Не стесняйтесь редактировать, если есть ошибка, поскольку я преобразовал ее из C и я не знаю php
function nCk($n, $k) { if( $n-$k<$k ) $k = $n-$k; $ans = 1; for( $i=1; $i<=$k; ++$i ) { $ans = ($ans*($n-$i+1))/$i; } return $ans; }
ИМО не стоит оптимизировать это, если не использовать ТЯЖЕЛЫЕ из-за ограничений по плавающей точке: 170! = 7.257415615308E + 306, а следующий факториал (171!) Выходит за пределы диапазона с плавающей запятой. Я предполагаю, что рекурсия будет замедлять процесс (но не проверять его).
function Factorial($x) { if ($x < 1) { echo "Factorial() Error: Number too small!"; }
Это неправильно, 0! = 1
0! = 1
, поэтому тест должен быть равен $x < 0
.
$ans = 1; for ($xx = 2; $xx >= $x; $xx++)
Вы опечатали условие, оно должно быть $xx <= $x
.
function Combination($selectcount,$availablecount) { $ans = Factorial($availablecount) / ( Factorial($availablecount - $selectcount) * Factorial($selectcount) ); return ($ans); }
У вас есть две потенциальные проблемы,
Factorial
функции происходит медленнее, чем цикл, вычисляющий счет комбинации здесь Являются ли эти фактические проблемы, зависит от вашего приложения. Вы написали, что результаты попадают в массив, по-видимому, чтобы избежать перерасчета, поэтому скорость для начального расчета менее важна. Однако проблемы с переполнением вполне могут быть. Чтобы избежать этого, вычислите рекурсивные записи массива на треугольник Паскаля, choose(n+1,k) = choose(n,k) + choose(n,k-1)
, где choose(n,k) = 0
если k < 0
или k > n
. В качестве альтернативы вы можете рассчитать каждую строку, начиная с choose(n,0) = 1
и choose(n,k) = choose(n,k-1)*(n+1-k)/k
для 1 <= k <= n
. Оба метода избегают большого промежуточного n!
и, таким образом, дают точные результаты для более широкого диапазона чисел.
Вам действительно не нужно вычислять полный числитель и знаменатель. Например:
C(7,2) = 7! / (2! * (7-2)!) = 7! / (2! * 5!) = (7 * 6) / (2 * 1)
То есть наибольший фактор знаменателя отменяет самую низкую часть факториала числителя. Так, например, если k> n / 2, все, что вам нужно сделать, это умножить числа от k + 1 на n и затем делить на (nk) !. Это экономит значительную работу по вычислению полного факториала.
Вот пример этого подхода:
function Combination($selectcount,$availablecount) { $remainder = $availablecount - $selectcount; if ($remainder > $selectcount) { $tmp = $remainder; $remainder = $selectcount; $selectcount = $tmp; } $ans = 1; while ($availablecount > $selectcount) { $ans *= $availablecount; $availablecount--; } while ($remainder > 1) { $ans /= $remainder; $remainder--; } return ($ans); }