1. Comprendi la complessità del tempo e la complessità dello spazio:
* Complessità temporale: Misura la quantità di tempo che un algoritmo impiega per completare in funzione della dimensione dell'input (spesso indicato come 'n'). Siamo interessati a * come * il tempo di esecuzione cresce, non il momento esatto in pochi secondi.
* Complessità dello spazio: Misura la quantità di memoria (o spazio) che un algoritmo richiede in funzione della dimensione di input 'n'. Ciò include lo spazio per i dati di input e le strutture di dati ausiliarie utilizzate dall'algoritmo.
2. Identificazione delle operazioni chiave:
* Operazioni di base: Identificare le operazioni che contribuiscono in modo significativo al tempo di esecuzione. Questi potrebbero includere:
* Operazioni aritmetiche (+, -, \*, /, %)
* Confronti (<,>, ==,! =)
* Assegnazioni (=)
* Accedi per array (arr [i])
* Assegnazioni e deallocazioni della memoria (a seconda della lingua)
* Concentrati sulle operazioni dominanti: Alcune operazioni saranno eseguite molto più frequentemente di altre. Concentrati su queste operazioni * dominanti *, in quanto avranno il maggiore impatto sul runtime complessivo. Ad esempio, in un algoritmo di smistamento, i confronti e gli swap sono dominanti.
3. Analizzare la struttura dell'algoritmo (procedura dettagliata del codice):
* Dichiarazioni sequenziali: Le dichiarazioni eseguite in sequenza contribuiscono con una quantità costante di tempo o spazio.
* Loops: La complessità temporale di un ciclo dipende da quante volte itera:
* Loop semplice (lineare): `Per i in gamma (n):...` esegue 'n' volte, quindi la complessità è O (n).
* Loop nidificati (quadratico, cubico, ecc.): `Per i in gamma (n):per j in gamma (n):...` esegue 'n * n' volte, quindi la complessità è O (n^2).
* Loop con comportamento logaritmico: `while n> 1:n =n // 2` esegue log2 (n) volte, quindi la complessità è O (log n).
* Loop con dipendenza dalla logica interna: Analizzare il corpo del ciclo per determinare la sua complessità e moltiplicarlo per il numero di iterazioni.
* Dichiarazioni condizionali (if/else):
* Analizza entrambi i blocchi `if` e` else`. La complessità complessiva è il * massimo * delle complessità dei blocchi `if` e` else`.
* Per strutture `if/else` profondamente nidificate, tracciare attentamente i possibili percorsi di esecuzione.
* RECUSIONE:
* Identifica i casi di base: Questi terminano la ricorsione.
* Identifica il passaggio ricorsivo: Come il problema viene suddiviso in sottoproblemi più piccoli.
* Scrivi una relazione di ricorrenza: Questo descrive matematicamente la complessità temporale della funzione ricorsiva. Ad esempio:`t (n) =t (n/2) + o (1)` (ricerca binaria)
* Risolvi la relazione di ricorrenza: Usa metodi come il teorema principale, la sostituzione o l'albero di ricorsione per trovare la complessità del tempo asintotico.
* Chiamate di funzione: Considera la complessità temporale della funzione chiamata. Se una funzione `A` chiama funzione` b`, la complessità temporale di `A` includerà la complessità temporale di` b '.
4. Esprimere complessità nella notazione asintotica (Big O, Big Omega, Big Theta):
* Big O Notation (O): Rappresenta il * Upper Bound * del tasso di crescita dell'algoritmo. Descrive lo scenario * peggiore *. "La complessità temporale dell'algoritmo è O (n^2)" significa che il runtime dell'algoritmo non crescerà * più veloce * di N^2 man mano che aumenta.
* Big Omega Notation (ω): Rappresenta il * limite inferiore * del tasso di crescita dell'algoritmo. Descrive lo scenario * best-case *. "La complessità temporale dell'algoritmo è ω (n)" significa che il runtime dell'algoritmo non crescerà * più lento * di 'n' man mano che aumenta.
* Big theta notation (θ): Rappresenta il * limite stretto * del tasso di crescita dell'algoritmo. Descrive lo scenario medio e quando il runtime dell'algoritmo cresce alla stessa velocità della funzione. "La complessità temporale dell'algoritmo è θ (n log n)" significa che il runtime dell'algoritmo crescerà allo stesso ritmo di n log n.
complessità temporali comuni (peggiore, big o):
* o (1):tempo costante: Il tempo di esecuzione è indipendente dalla dimensione dell'input. Esempio:accedere a un elemento in un array con il suo indice.
* o (log n):tempo logaritmico: Il tempo di esecuzione aumenta logaritmicamente con la dimensione dell'input. Esempio:ricerca binaria in un array ordinato.
* o (n):tempo lineare: Il tempo di esecuzione aumenta linearmente con la dimensione dell'input. Esempio:ricerca di un elemento in un array non orientato.
* o (n log n):tempo linearitmico: Comune in efficienti algoritmi di smistamento. Esempio:unisci Ordine, QuickSort (caso medio).
* o (n^2):tempo quadratico: Il tempo di esecuzione aumenta quadraticamente con la dimensione dell'input. Esempio:ordinamento a bolle, ordinamento di inserimento.
* o (n^3):tempo cubico: Esempio:moltiplicazione di matrice.
* o (2^n):tempo esponenziale: Il tempo di esecuzione raddoppia con ogni aggiunta alla dimensione dell'input. Spesso indica un approccio bruto-forza. Esempio:provare tutti i possibili sottoinsiemi di un set.
* o (n!):Tempo fattoriale: Il tempo di esecuzione cresce molto rapidamente. Esempio:trovare tutte le permutazioni di un set.
5. Ignora fattori costanti e termini di ordine inferiore:
Quando si usano la notazione asintotica, siamo principalmente interessati al termine * dominante * che determina il tasso di crescita. Fattori costanti e termini di ordine inferiore diventano insignificanti man mano che 'n' diventa molto grande.
* `3n^2 + 5n + 10` è semplificato a` o (n^2) `
* `100 log n + n` è semplificato in` o (n) `(n cresce più velocemente del log n)
* `2^n + n^5` è semplificato a` o (2^n) `
6. Analizza la complessità dello spazio:
Simile alla complessità del tempo, analizzare lo spazio utilizzato dall'algoritmo. Considerare:
* Spazio di input: Lo spazio richiesto per archiviare i dati di input.
* Spazio ausiliario: Lo spazio extra utilizzato dall'algoritmo, oltre lo spazio di input, per variabili, strutture di dati e stack di chiamate di ricorsione.
Esempi:
* Ricerca lineare:
`` `Python
def linear_search (arr, target):
per i in gamma (len (arr)):
Se arr [i] ==target:
ritorno i
restituzione -1
`` `
* Complessità temporale: O (N) (Lineare, caso peggiore:Target non è nell'array o è l'ultimo elemento)
* Complessità dello spazio: O (1) (costante, perché utilizza solo poche variabili extra)
* Ricerca binaria:
`` `Python
def binary_search (arr, target):
basso =0
alto =len (arr) - 1
Mentre basso <=alto:
mid =(basso + alto) // 2
Se arr [mid] ==target:
ritorno a metà
elif arr [mid]
altro:
alto =Mid - 1
restituzione -1
`` `
* Complessità temporale: O (log n) (logaritmic)
* Complessità dello spazio: O (1) (versione costante, iterativa)
* Ricerca binaria ricorsiva:
`` `Python
def binary_search_recursive (arr, target, basso, alto):
se basso> alto:
restituzione -1
mid =(basso + alto) // 2
Se arr [mid] ==target:
ritorno a metà
elif arr [mid]
altro:
return binary_search_recursive (arr, target, basso, metà - 1)
`` `
* Complessità temporale: O (log n)
* Complessità dello spazio: O (log n) a causa della profondità di ricorsione. Ogni chiamata ricorsiva aggiunge un nuovo frame allo stack di chiamata.
In sintesi:
1. Definisci ciò che vuoi misurare: Complessità temporale, complessità spaziale o entrambi.
2. Identifica le operazioni chiave contribuendo all'utilizzo delle risorse.
3. Analizzare la struttura dell'algoritmo (Loops, Conditionals, Recorsion).
4. Esprimi la complessità Usando Big O, Big Omega o Big theta notazione.
5. Ignora fattori costanti e termini di ordine inferiore.
Seguendo questi passaggi, è possibile analizzare efficacemente l'efficienza degli algoritmi e scegliere l'algoritmo più adatto per le tue esigenze specifiche. Ricorda che l'algoritmo migliore dipende dal problema specifico, dalle dimensioni dei dati di input e dalle risorse disponibili.
Informazioni correlate
software © www.354353.com