2013-03-08 13 views
26

Qual è il metodo più efficiente per valutare il valore di n scegliere k? Il modo di forza bruta che penso sarebbe quello di trovare fattoriale/k fattoriale/(n-k) fattoriale.Calcolare il valore di n scegliere k

Una strategia migliore potrebbe essere l'utilizzo di dp in base a questo recursive formula. C'è qualche altro metodo migliore per valutare n scegliere k?

+0

Il calcolo fattoriale è molto più efficiente della tua alternativa ricorsiva in termini di spazio e di tempo – SomeWittyUsername

+2

Bene, per cominciare puoi sostituire 'n!/K!' Con 'n * (n-1) * (n-2) * ... * (k + 1) 'Nessun punto nel calcolo di' n! 'e' k! 'per intero quando molti fattori si annullano. –

+0

Quale gamma di n stai considerando? –

risposta

23

si potrebbe usare la formula moltiplicativo per questo:

enter image description here

http://en.wikipedia.org/wiki/Binomial_coefficient#Multiplicative_formula

+14

possiamo accelerarlo calcolando (n scegliere n-k) invece di (n scegliere k) quando n-k

+1

Basta considerare che '(n - (ki))/i'may non è un numero intero – pomber

+1

@pomber il singolo fattore' (n - (ki))/i' potrebbe in effetti non essere un numero intero MA il prodotto di i fattori da 'x = 1 fino a y' saranno sempre divisibili per' y' (perché ci sono esattamente y numeri interi consecutivi) – Lambder

-1

"Più efficiente" è una richiesta scadente. Cosa stai cercando di rendere efficiente? Lo stack? Memoria? Velocità? Nel complesso, la mia opinione è che il metodo ricorsivo sia il più efficiente perché utilizza solo l'aggiunta (un'operazione a basso costo) e la ricorsione non sarà troppo negativa per la maggior parte dei casi. La funzione è:

nchoosek(n, k) 
{ 
    if(k==0) return 1; 
    if(n==0) return 0; 
    return nchoosek(n-1, k-1)+nchoosek(n-1,k); 
} 
+1

Questa è una ricorsione ad albero e per grandi valori per n e k potrebbe non finire affatto. – Pedrom

+5

È il tempo 'O (2^n)' e lo spazio 'O (n)'. Il calcolo fattoriale è 'O (n)' tempo e 'O (1)' spazio. – SomeWittyUsername

+0

Non sono d'accordo. Questo è effettivamente il calcolo del triangolo di Pascal; è sicuramente ** NOT ** 'O (2^n)' - è 'O (n^2)'. Questo è un quadrato, non un albero. Inoltre, non verrà mai traboccato se il risultato è memorizzabile a lungo, e l'aggiunta è molto più rapida della moltiplicazione e della divisione. Ancora di più, puoi memoize con 'f (n, k) = f (n, n-k)', e tutti i casi limite sono 1. –

0

Se si dispone di una tabella di ricerca dei fattoriali allora il calcolo di C (n, k) sarà molto veloce.

+0

Per il grande numero di n e k quella tabella di ricerca potrebbe essere proibitiva. Inoltre dovrebbe esserci un'opzione per i valori al di fuori di quella tabella. – Pedrom

+0

@Pedrom Non è stata menzionata la limitazione della grandezza dei numeri nella domanda. È etichettato come "linguaggio indipendente" e "algoritmi". –

0

Il problema con l'approccio n!/k!(n-k)! non è tanto il costo come la questione con ! crescendo molto rapidamente in modo che, anche per valori di nCk che sono ben all'interno della portata, per esempio, interi a 64-bit, calcoli intermedi sono non. Se non vi piace l'approccio Inoltre ricorsivo di kainaw si potrebbe provare l'approccio moltiplicativo:

nCk == product(i=1..k) (n-(k-i))/i

dove product(i=1..k) si intende il prodotto di tutti i termini quando i assume i valori 1,2,...,k.

+0

Hai ragione riguardo alla possibilità che l'overflow corrompa le cose molto prima che la risposta finale smetta di adattarsi a una parola macchina, ma la soluzione non mi piace: produrrà valori frazionari per alcuni fattori, ad es. per il fattore i = 2 quando n = 4 e k = 3. (Ovviamente i fattori si moltiplicheranno alla fine per dare un numero intero, ma il tuo modo significa che i risultati intermedi devono essere memorizzati in virgola mobile - schifo!) –

4

Probabilmente il modo più semplice per calcolare i coefficienti binomiali (n choose k) senza overflow è utilizzare il triangolo di Pascal. Non sono necessarie frazioni o moltiplicazioni. (n choose k). La riga nth e la voce kth del triangolo di Pascal forniscono il valore.

Take a look at this page. Si tratta di un'operazione O(n^2) con solo aggiunta, che è possibile risolvere con la programmazione dinamica. Sarà veloce come un fulmine per qualsiasi numero che possa rientrare in un numero intero a 64 bit.

+2

e O (n^2) spazio aggiuntivo – SomeWittyUsername

+0

@rici corretto, ma sarebbe un'ottimizzazione del metodo presentato – SomeWittyUsername

+0

So che è vecchio, ma questo certamente non richiede spazio O (n^2). L'ho appena implementato usando O (k) space. Mostrerà la mia soluzione, se ti va. –

2

Se avete intenzione di calcolare molte combinazioni come questo, calcolando Triangolo del Pascal è che l'opzione migliore. Come già sapete la formula ricorsiva, penso di poter passato un certo codice qui:

MAX_N = 100 
MAX_K = 100 

C = [[1] + [0]*MAX_K for i in range(MAX_N+1)] 

for i in range(1, MAX_N+1): 
    for j in range(1, MAX_K+1): 
     C[i][j] = C[i-1][j-1] + C[i-1][j]; 

print C[10][2] 
print C[10][8] 
print C[10][3] 
35

Ecco la mia versione, che funziona esclusivamente in numeri interi (la divisione per k produce sempre un quoziente intero) ed è veloce a O (k):

function choose(n, k) 
    if k == 0 return 1 
    return (n * choose(n - 1, k - 1))/k 

ho scritto in modo ricorsivo perché è così semplice e carina, ma si potrebbe trasformare in una soluzione iterativa, se volete.

+1

Non è O (* k *); * k * è strettamente inferiore a * n *, quindi non è possibile ignorare il contributo di * n * al tempo di esecuzione. Nella migliore delle ipotesi, puoi dire che è O (* k * M (* n *)), dove M (* n *) è la velocità dell'algoritmo di moltiplicazione. – chepner

+3

Corretto, ma pedante. La funzione sopra indicata fa moltiplicazioni e divisioni O (k). Ho ignorato la complessità delle operazioni stesse. – user448810

+0

Questa funzione calcola 'n!/K!'. Non è quello che la domanda è su – SomeWittyUsername

0

Il modo più veloce è probabilmente utilizzare la formula, e non il triangolo di pascal. Iniziamo a non fare moltiplicazioni quando sappiamo che stiamo andando a dividere per lo stesso numero più tardi. Se k < n/2, abbiamo k = n - k.Sappiamo che C (n, k) = C (n, n-k) Ora:

n!/(k! x (n-k)!) = (product of numbers between (k+1) and n)/(n-k)! 

Almeno con questa tecnica, non si è mai dividere per un numero che è stato utilizzato per moltiplicare prima. Hai le moltiplicazioni (n-k) e le divisioni (n-k).

Sto pensando a un modo per evitare tutte le divisioni, trovando GCD tra i numeri che dobbiamo moltiplicare e quelli che dobbiamo dividere. Proverò a modificare più tardi.

+0

Trovare il GCD ridurrà sicuramente la quantità di operazioni. Sfortunatamente, la scoperta GCD di per sé sarebbe un compito molto più pesante. – SomeWittyUsername

+0

Sì, ho paura di questo. Ma i GCD sarebbero calcolati su piccoli numeri, quando la moltiplicazione ha un grande numero. E in realtà non sono sicuro che un GCD sia più difficile di una divisione. –

+0

Tendo ad essere scettico, ma sarebbe interessante vedere i risultati :) – SomeWittyUsername

Problemi correlati