9 votos

¿Cómo simplificar o calcular una fórmula con factoriales muy grandes?

Estoy enfrentando un problema práctico donde he calculado una fórmula que, con la ayuda de programación, puede llevarme a mi respuesta final. Sin embargo, los números involucrados son tan grandes que tarda una eternidad en calcularse, y creo que podría no ser necesario. Tengo la siguiente fórmula,

$\sum\limits^{k}_{i=m}(N-i)^{k-i}(\frac{1}{N})^k\frac{k!}{(k-i)!i!} \leq a$

de la cual necesito calcular N. El resto de los valores son constantes, pero en el rango de las 100,000. El factorial me está causando dolor de cabeza, ya que los valores involucrados son demasiado grandes; ¿qué simplificaciones podría hacer que aflojarán ligeramente los límites y por lo tanto simplificarán el cálculo? ¿Hay trucos estándar? ¿O quizás una manera de calcular esto en matlab / octave?

11voto

Shabaz Puntos 403

Necesitas la aproximación de Stirling. Es muy precisa para factoriales grandes.

0 votos

La aproximación de Stirling no ayudaría en absoluto. Las mismas restricciones se aplican para esta fórmula también. ¿Qué tiene de bueno tener que calcular 100000 en el exponente?

0 votos

@PanosK.: No veo ningún $N!$ allí. El siguiente paso sería utilizar la aproximación normal para el término $k \choose i$.

0 votos

Sí, de hecho no hay factoriales allí. Estoy hablando de computadoras. Se ve mejor en papel y es mejor cuando hago los cálculos. pero la complejidad, o aún mejor el tiempo y la memoria necesarios para calcular este número (100000) no tenía una implementación práctica en mi calculadora de teléfono móvil, por ejemplo. Puedo calcular máximo 480! Con la aproximación de Stirling es exactamente lo mismo.

7voto

Lars Truijens Puntos 24005

No es necesario calcular los factoriales individuales para calcular $k!/(k-i)!i!$, ya que eso es el coeficiente binomial $\binom{k}{i}$. Un algoritmo simple para calcular coeficientes binomiales se puede encontrar en Wikipedia. Un algoritmo más sofisticado es el de Goetgheluck (JSTOR); las implementaciones se pueden encontrar aquí y aquí.

Por supuesto, con números del tamaño que tienes, esto aún puede no ser factible, y en este caso también recomiendo la fórmula de Stirling.

2voto

Aquí hay un buen artículo sobre la implementación de la aproximación de Stirling, junto con una implementación de referencia:

http://threebrothers.org/brendan/blog/stirlings-approximation-formula-clojure/

2voto

user182100 Puntos 11
 import java.util.HashMap;

/**
 * Métodos de conveniencia para combinaciones determinísticas.
 * 
 * Uso:
 *  
 * Factorial f = new Factorial();
 * long ten_over_two = f.over(10,2);     //tarda un poco, ya que no hay caché todavía
 * System.out.println( f.factorial( 8)); //instantáneo
 * 
 *  
 * Notas:
 * instanciar y usar, reutilizará su propia caché, por lo que después de un tiempo se supone que se ejecutará en O(1).
 *     El código es súper rápido (0.0ms)
 * Para números muy grandes, use la aproximación de Stirling, @see http://en.wikipedia.org/wiki/Stirling%27s_approximation
 * El desbordamiento es un gran problema. 
 *     el valor máximo es 9223372036854775807 < 21!, 
 *     1.7976931348623157E308 <171!
 *     Ya sea que sea súper rápido o se rompa, las combinaciones crecen demasiado rápido, y para 2k~n es el mayor; 
 *     ni siquiera puede manejar (29 14) pero para k/n pequeño puede funcionar apenas.
 * 
 */
public class Factorial {

    static private HashMap cache = new HashMap (); 

    public Double factorial(int n)
    {
    Integer N = Integer.valueOf( n );
    if(cache.containsKey( N) ) return cache.get( N );

    if(n<1) {
        cache.put( N, 0D );
        return 0D;
    }
    if(n==1) {
        cache.put( N, 1D );
        return 1D;
    }

    Double temp = 1D;
    for (int i = 1; i < n; i++) {
        temp*=i;
        cache.put( i, temp );
    }

    Double prev = factorial(n-1);
    if(prev>Double.MAX_VALUE/n) throw new IllegalArgumentException("factorial("+n+") overflow");

    return n*prev;
    }

    // 52!/47! = 52*51*50*49*48 so extra(52,47) = 52*(51,47) ... and (47,47)=1
    private Double extra(int n, int minus)
    {
        if(n<=minus) return 1D;
        Double result = extra(n-1,minus);
        if(result>Double.MAX_VALUE/n) throw new IllegalArgumentException(  "overflow");
        return n* result;
    }

    /**
     * 
     * Este método calcula (n k) = n!/(k! * (n-k)!) 
     * 
     * Ejemplo de uso:
     * over(52,2) = 2598960
     * over(5,2) = 10
     * over(2,2) = 1 //ignorando intercambios
     *
     * @param n el conjunto grande
     * @param k cuántos sacar de n
     * @return todas las combinaciones de sacar k de n, ignorando intercambios. Simplemente doble la cantidad para tener en cuenta intercambios.
     *
     */
    public Double over(int n, int k){
        if(k==n) return 1D;
        if(k>n) return 0D;
        if(k<=0) return 0D;
        //k>=1 and n>k
        if(k

0 votos

¿Por qué sería más rápido?

0 votos

Porque reutiliza factores ya calculados.

1voto

Polarbear0106 Puntos 1

No estoy seguro cuál es la pregunta, pero aquí va. Vamos a suponer que estás intentando que una computadora realice el cálculo. 1M! tendría un exponente de < 10^6M. No estoy seguro acerca del software disponible para un exponente tan grande. Si tomas el logaritmo para calcular, entonces el número es simplemente < 6M. Los logaritmos también tienen la ventaja de ser suma al multiplicar, por lo que hacer una M de suma no es tan complicado. También podrías establecer la función entre i y k, lo cual aceleraría las cosas, asumiendo un tamaño bastante grande de i. Adicionalmente podrías buscar el valor de 100K! (2.824229408 × 10 456573 cuyo logaritmo es 456573.450899970914458) y luego simplemente iterar en valores mayores que ese.

i-Ciencias.com

I-Ciencias es una comunidad de estudiantes y amantes de la ciencia en la que puedes resolver tus problemas y dudas.
Puedes consultar las preguntas de otros usuarios, hacer tus propias preguntas o resolver las de los demás.

Powered by:

X