8

Ho cercato in alto e in basso e non riesco a trovare molto materiale relativo a complessità di run-time, ricorsione e java.Complessità di run-time per algoritmi ricorsivi

Attualmente sto imparando le complessità di run-time e la notazione Big-O nella mia classe Algorithms e sto riscontrando problemi nell'analisi degli algoritmi ricorsivi.

private String toStringRec(DNode d) 
{ 
    if (d == trailer) 
     return ""; 
    else 
     return d.getElement() + toStringRec(d.getNext()); 
} 

Questo è un metodo ricorsivo che semplicemente eseguirà l'iterazione attraverso una lista doppiamente collegata e stamperà gli elementi.

L'unica cosa che posso venire con è che ha una complessità di run-time di O (n), dal momento che il numero di chiamate metodo ricorsivo dipenderà dal numero di nodi nel dlist, ma ho ancora don' t sentirsi a proprio agio con questa risposta.

Non sono sicuro di dover calcolare l'aggiunta di d e d.getNext().

Oppure sono completamente fuori strada e la complessità del tempo di esecuzione è costante, poiché tutto ciò che sta facendo è recuperare elementi dallo DNodes nello DList?

+0

Vedere: http://stackoverflow.com/questions/1126388/slow-string-concatenation-over-large-input – dyoo

risposta

0

L'algoritmo presenta la complessità di runtime di O (n) come suggerito. L'elenco contiene n elementi e l'algoritmo eseguirà una quantità di lavoro quasi obbligata per ciascun elemento (che funziona come accesso Element e Next, oltre a una nuova chiamata toStringRec). Il recupero di un elemento da un DNode richiede tempo costante e i tempi costanti vengono scartati nella notazione O grande.

La cosa interessante dei metodi ricorsivi (nella maggior parte dei casi) è che anch'essi sono anche O (n) nella complessità dello spazio. Una nuova voce di stack (per memorizzare i parametri passati al metodo) viene creata per ogni chiamata a toStringRec, che viene chiamata n volte.

+1

Questa spiegazione fornisce purtroppo una conclusione errata. Non tiene conto del costo della concatenazione di stringhe. Cioè, il costo per ogni articolo è ** non costante **. Questo è un punto importante di questo problema. Per favore correggi questo. – dyoo

+0

Concordo sul fatto che il costo di ogni articolo non è costante, ma non sono d'accordo che sia O (n). Il costo di 'stringa1 + stringa2' è O (m), dove m è la lunghezza della stringa risultante. Nello specifico, concatenare due stringhe è nel peggiore dei casi la creazione di un nuovo carattere [] di lunghezza m e la copia una tantum di ciascun carattere delle stringhe originali. Quando all'ennesima iterazione del codice fornito, toStringRec potrebbe restituire una stringa molto lunga, ma il costo della concatenazione è ancora O (m). m non è direttamente legato a n in questo esempio, dato che 'getElement' può restituire stringhe vuote o molto lunghe. – sgmorrison

+0

Supponiamo che ci sia una certa lunghezza 'm' che è un limite superiore alla dimensione di un particolare d.getElement(). Quindi la dimensione della stringa restituita da un 'toStringRec (nodo)' è vincolata dalla lunghezza della catena che inizia da 'node'. Sia 'T (n)' il costo del calcolo. Quindi: 'T (n) dyoo

3

A prima vista, questo sembra un classico caso di tail recursion modulo cons, una generalizzazione di coda chiamata. È equivalente a un ciclo con il numero di iterazioni.

Tuttavia, non è così semplice: la cosa difficile qui è l'aggiunta di d.getElement() in una stringa in crescita: questo è di per sé un lineare operazione , e si ripete N volte. Quindi la complessità della tua funzione è O(N^2).

+0

Hmm, pensavo che d.getElement() fosse quello di ottenere i dati memorizzati sul nodo d. Ha bisogno di rendere la sua domanda un po 'più chiara su questo credo ... –

+2

@XiaoChuanYu No, 'd.getElement()' è 'O (1)'. È la concatenazione di stringhe che segue che è lineare. – dasblinkenlight

+0

Sì, grazie per non aver ignorato il costo della concatenazione di stringhe. Questo è esattamente giusto. La somma di gauss '1 + 2 + ... + n' entra in gioco, ed è da lì che proviene il quadratico. – dyoo

2

Se T (n) è il numero di operazioni elementari (in questo caso - quando si entra nel corpo della funzione, una delle righe all'interno sono eseguiti al massimo una volta per tutte, ma il secondo ritorno non è O (1)) eseguito chiamando toStringRec su un elenco di n elementi, quindi

T(0) = 1 - as the only things that happen is the branch instruction and a 
       return 
    T(n) = n + T(n-1) for n > 0 - as the stuff which is being done in the 
       function besides calling toStringRec is some constant time stuff and 
       concatenating strings that takes O(n) time; and we also run 
       toStringRec(d.getNet()) which takes T(n-1) time 

a questo punto abbiamo descritto la complessità dell'algoritmo. Ora possiamo calcolare la forma chiusa per T, T (n) = O (n ** 2).

+0

No: il "materiale fatto nella funzione" non è O (1).È un lavoro che richiede tempo proporzionale a 'n', assumendo che la dimensione della stringa su ciascun elemento non sia vuota. Quindi, la forma chiusa per 'T (n)' finisce per apparire come 'T (n) = 1C + 2C + 3C + ... + nC' per qualche costante C. Questa è una somma di Gauss. 'T (n)' è quadratico, non lineare. – dyoo

+0

Buona cattura, grazie! –

2

Questo è un esempio piuttosto semplice, ma il trucco è definire una relazione di ricorrenza, che è una funzione del tempo di esecuzione di una determinata dimensione di input in termini di dimensioni di input più piccole.Per questo esempio, supponendo che il lavoro svolto ad ogni passo richiede costante di tempo C e supponendo che il caso base non lavora, sarebbe:

T(0) = 0 
T(n) = C + T(n-1) 

È quindi possibile risolvere per l'esecuzione tempo con sostituzione di trovare una serie :

T(n) = C + T(n-1) = 2C + T(n-2) = 3C + T(n-3) = ... = nC + T(n-n) = nC + 0 = nC 

Con la definizione di O, questa equazione è O (n). Questo esempio non è particolarmente interessante, ma se si guarda qualcosa come il runtime di mergesort o un altro algoritmo di divisione e conquista è possibile ottenere un'idea migliore delle relazioni di ricorrenza.

+0

Ovviamente in questo esempio è possibile anche percepirlo: si stampa su ogni nodo in un elenco collegato, quindi il numero di stampe che si esegue cresce esattamente alla stessa velocità della dimensione dell'elenco. Quindi è il tempo lineare. – cjm

+1

In questo particolare esempio, non possiamo supporre che il lavoro svolto ad ogni passo sia costante, dato come funziona la concatenazione delle stringhe in Java. – dyoo

+0

Penso che sia giusto formulare questa ipotesi perché il problema di questo problema non è la ricerca della complessità delle funzioni della libreria Java, ma la comprensione di come questo tipo di algoritmo ricorsivo possa essere analizzato in generale. – cjm

0

Per tali algoritmi ricorsivi, è in genere possibile scrivere un'equazione ricorsiva per calcolare l'ordine. È consuetudine mostrare il numero di istruzioni eseguite con T (n). In questo esempio, abbiamo:

T (n) = T (n - 1) + O (1)

(Supponendo che la funzione getElement gira in tempo costante.) La soluzione di questa equazione è banalmente T (n) = O (n).

Questo era il caso generale. Tuttavia, a volte è possibile analizzare l'algoritmo senza scrivere tale equazione. In questo esempio, puoi facilmente sostenere che ogni elemento è visitato al massimo una volta, e ogni volta che viene eseguito un lavoro a tempo costante; quindi, occore O (n) nel complesso per fare il lavoro.