









Studia grazie alle numerose risorse presenti su Docsity
Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium
Prepara i tuoi esami
Studia grazie alle numerose risorse presenti su Docsity
Prepara i tuoi esami con i documenti condivisi da studenti come te su Docsity
Trova i documenti specifici per gli esami della tua università
Preparati con lezioni e prove svolte basate sui programmi universitari!
Rispondi a reali domande d’esame e scopri la tua preparazione
Riassumi i tuoi documenti, fagli domande, convertili in quiz e mappe concettuali
Studia con prove svolte, tesine e consigli utili
Togliti ogni dubbio leggendo le risposte alle domande fatte da altri studenti come te
Esplora i documenti più scaricati per gli argomenti di studio più popolari
Ottieni i punti per scaricare
Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium
appunti per svolgere al meglio l'esame scritto
Tipologia: Prove d'esame
Offerta a tempo limitato
Caricato il 26/01/2017
5
(2)1 documento
1 / 16
Questa pagina non è visibile nell’anteprima
Non perderti parti importanti!










In offerta
COSA è un ALGORITMO? Sequenza finita di passi interpretabili da un esecutore. CLASSIFICAZIONE DEGLI ALGORITMI
Non deterministici: ad ogni punto di scelta, esplorano tutte le vie contemporaneamente. CORRETTEZZA DI UN ALGORITMO RISPETTO AD UN PROBLEMA Esistono vari problemi reali che possono essere risolti attraverso l’ uso di determinati algoritmi (problem solving).
Dati un problema e un algoritmo, l’algoritmo è detto corretto rispetto al problema se, per ogni istanza dei dati di ingresso del problema, l’algoritmo termina e produce la soluzione corrispondente.
COMPLESSITA’ di un ALGORITMO rispetto all’uso di RISORSE Esistono più algoritmi equivalenti per la soluzione di un problema, ma l’astuzia sta nel confrontarli e nel trovare quello che è più conveniente dal punto di vista:
Le strutture dati costituiscono gli ingredienti di base degli algoritmi. L’efficienza di un algoritmo dipende in maniera critica dal modo in cui sono organizzati i dati su cui esso deve operare.
Una struttura dati `e un insieme di dati logicamente correlati e opportunamente memorizzati, per i quali sono deniti degli operatori di costruzione, selezione e manipolazione.
Quattro classi fondamentali : array, liste, alberi e gra.
PROBLEMA DELLA DECIDIBILITA’
Purtroppo non `e sempre possibile trovare un algoritmo che risolve un problema dato. Di conseguenza i problemi si dividono in decidibili e indecidibili a seconda che possano essere risolti oppure no.
DECIDIBILE:
Se esiste un algoritmo che assegna una soluzione ad una configurazione di ingressi in tempo finito. Questi tipi di problemi sono classificati in base alla loro trattabilità(possibilità di risolvere un problema in maniera efficiente).
Un problema di decisione decidibile viene classificato come intrattabile se non è risolvibile in tempo polinomiale nemmeno da un algoritmo non deterministico. Questo tipo si divide in :
P è un sottoinsieme di NP.
/* Esiste una soluzione al problema */
INDECIDIBILE:
Se non esiste un algoritmo che assegna una soluzione ad una configurazione di ingressi in tempo finito. In tal caso sarà possibile calcolare una soluzione in tempo finito solo per alcune delle istanze dei dati di ingresso.
È un risultato fondamentale nello studio della complessità della risoluzione dei problemi intrattabili e si basa sul concetto di riducibilità in tempo polinomiale tra problemi.
Dati due problemi di decisione:
Un array è una struttura statica e omogenea, nel senso i cui elementi non variano di numero a tempo d’esecuizione e tutti dello stesso tipo. L’accesso a ciascun elemento di un array ha complessità asintotica O (1).
Problemi di:
void visita(int a[], int n)
{
Int i; for ( i = 0; i < n; i++ ) { elabora( a[i]); }
}
Vediamo che la complessità dell’algoritmo è O(n), perché dipende dal numero di elementi dicui è composto l’array.
Int ricerca_lineare(int a[], int n, int valore)
{
Int i; for( i = 0; ((i < n) && (a[i] != valore)); i++ ) return(( i < n )? i : -1 );
}
Se l’algoritmo trova il valore ricercato nell’array allora la ricerca si blocca e la funzione ritornerà la posizione dell’array in cui si trova il valore ricercato, altrimenti la funzione ritornerà -1.
CASO PESSIMO: se il valore cercato non è presente nell’array e quindi l’algoritmo avrà complessità O(n).
CASO OTTIMO: il valore cercato è il primo elemento dell’array e quindi l’algoritmo avrà complessità O(1).
ALGORITMO DI RICERA BINARIA PER ARRAY ORDINATI
Se un array è già ordinato, un elemento può essere anche ricercato con l’uso del seguente algoritmo.
Si divide l’array in due parti uguali(dx e sx)e si confronta il valore ricercato con il valore che sta a metà, in questo modo se il valore ricercato è maggiore del mediano allora si dividerà nuovamente la parte a destra e si continuerà così via fino a quando non si trova il valore ricercato.
Int ricerca_binaria(int a[], int n,int valore)
{
Int sx,dx,mx; for( sx = 0, dx = n-1, mx = (sx + dx)/2; ((sx <= dx)&&(a[x] != valore)); mx = (sx + dx)/2) if ( a[mx] > valore) dx = mx – 1; else sx = mx + 1; return((s <= dx)? mx :-1);
}
CASO PESSIMO: Se non viene trovato alcun elemento dopo aver dimezzato ripetutamente l’array in questione. T(n) = O(log n).
CASO OTTIMO:Se viene torvato al primo dimezzamento come elemento mediano e ha quindi complessità T(n) = O(1).
L'algoritmo solitamente ordina la sequenza sul posto. Si assume che la sequenza da ordinare sia partizionata in una sottosequenza già ordinata, all'inizio composta da un solo elemento, e una ancora da ordinare. Alla -esima iterazione, la sequenza già ordinata contiene elementi. In ogni iterazione, viene rimosso un elemento dalla sottosequenza non ordinata (scelto, in generale, arbitrariamente) e inserito (da cui il nome dell'algoritmo) nella posizione corretta della sottosequenza ordinata, estendendola così di un elemento.
CASO PESSIMO : quello in cui la sequenza di partenza sia ordinata al contrario. In questo caso, ogni iterazione dovrà scorrere e spostare ogni elemento della sottosequenza ordinata prima di poter inserire il primo elemento della sottosequenza non ordinata. Pertanto, in questo caso l'algoritmo ha complessità temporale quadratica, ossia T(n) = O(n^2).
In uno heap decrescente (utilizzato per ordinare ad esempio un array in senso crescente) ogni nodo padre contiene un valore maggiore o uguale a quello dei suoi due figli diretti, di conseguenza risulterà maggiore anche di tutti i nodi che si trovano nel sottoalbero di cui esso è la radice; questo non implica affatto che nodi a profondità maggiore contengano valori minori di quelli a profondità minore. Quindi in ogni istante, in un heap decrescente, la radice contiene il valore maggiore. L'algoritmo che ordina in senso crescente inizia creando uno heap decrescente. Per ogni iterazione si copia la radice (primo elemento dell'array) in fondo all'array stesso, eseguendo uno scambio di elementi. L'algoritmo poi ricostruisce uno heap di elementi spostando verso il basso la nuova radice, e ricomincia con un altro scambio (tra il primo elemento dell'array e quello in posizione ), eseguendo un ciclo che considera array di dimensione progressivamente decrescente.
Si può dimostrare che la complessità asintotica massima dell'heapsort è. Tuttavia, in generale (e soprattutto perarray quasi ordinati) altri algoritmi con la medesima complessità asintotica, per esempio quick sort o merge sort, ottengono migliori prestazioni.
Una lista è una tripla L = (E,t,S) dove E è un insieme di elementi, t € E è detto testa ed S è una relazione binaria su E, dove S è sottinsieme di E * E che soddisfa le seguenti proprietà:
S; /* ogni elemento diverso dalla testa è collegato ad un solo elemento e’ */
/* ogni elemento è collegato al massimo ad un solo elemento e’ */
e1’ = t, (ei’, ei+1’) € S per ogni 1 <= i <= k – 1, ek’ = e. Una lista è rappresentata come una struttura dinamica lineare, in cui ogni elemento contiene un valore ed un puntatore all’elemento successivo,(se singolarmente collegata)ed anche all’elemento precedente (se doppiamente collegata). Ogni elemento della lista contiene la chiave dell’elemento successivo. Se la lista è singolarmente collegata:
Se è doppiamente collegata:
Fa ad eccezione se la lista è circolare. ALGORITMO DI VISITA, RICERCA, INSERIMENTO e RIMOZIONE per LISTE. void visita_vista(elem_lista *testa_p) { elem_lista *elem_p; for (elem_p = testa_p; elem_p != NULL; elem_p = elem_p -> succ_p) elabora(elem_p -> valore); } T(n) = O(n);
elem_lista *cerca_in_lista(elem_lista *testa_p, int valore) { Elem_lista *elem_p; for(elem_p = testa_p;((elem_p != NULL)&&(elem_p->valore != valore);elem_p = elem_p -> succ_p) return(elem_p); }
Int inserisci_in_lista(elem_lista *testa_p,int valore) { Elem_lista *corr_p, prec_p, nuovo_p; for(corr_p = prec_p = *testa_p; ((corr_p != NULL) &&(corr->valore < valore)); prec_p = corr_p, corr_p = corr_p -> succ_p) if ((corr_p != NULL) && (corr_p -> valore == valore)) inserito = 0; else { Inserito = 1; nuovo_p = (elem_lista *)malloc(sizeof(elem_lista)); nuovo_p -> valore = valore; nuovo_p -> succ_ p = corr_p; if ( corr_p == *testa_p) *testa_p = nuov_p; else prec_p -> succ_p =nuovo_p; } Return(inserito); }
If( nodo_p != NULL) { Elabora(nodo_p -> valore); visita_albero_bin_ant(nodo_p -> sx); visita_albero_bin_ant(nodo_p -> dx); } } Nella visita in ordine simmetrico si visita prima il sottoalbero sinistro , poi il nodo e poi il sottoalbero destro. Void visita_albero_bin_simm( nodo_albero * nodo_p) { If( nodo_p != NULL) { visita_albero_bin_ant(nodo_p -> sx); Elabora(nodo_p -> valore); visita_albero_bin_ant(nodo_p -> dx); } }
posticipato: si visita prima il sottoalbero sinistro, poi quello destro e poi il nodo. Void visita_albero_bin_post( nodo_albero * nodo_p) { If( nodo_p != NULL) { visita_albero_bin_ant(nodo_p -> sx); visita_albero_bin_ant(nodo_p -> dx); Elabora(nodo_p -> valore); } } Se a questo punto indichiamo con:
T(n) = 1 + T(k) + T(n – 1 – k ) + d;
Alla fine dipenderà da n perché se vediamo per n = 0 T(n) = 1 e quindi anche la d deve dipendere da n. T(n) = O(n). I precedenti algoritmi di visita possono essere trasformati in algoritmi di ricerca che hanno una COMPLESSITA’ T(n) = O(1) per il CASO OTTIMO.(valore presente nella radice o albero vuoto) COMPLESSITA’ T(n) = O(n) per il CASO PESSIMO. (valore non presente nell’albero binario) ALGORITMO DI RICERA BINARIA PER ALBERI ORDINATI
Come per gli array si poteva usufruire di un algoritmo di ricerca per array ordinati che quando eseguito ha un guadagno rispetto al tempo delle altre ricerche, anche negli alberi si può guadagnare tempo durante la ricerca usufruendo del seguente algoritmo: Se ogni nodo ha valore k. Tutti i valori presenti nei loro sotto alberi di sinistra saranno <= k. A destra invece >= k. Insieme agli alg. di inserimento e rimoz. ha un CASO MEDIO T(n) = O(log n). Se si vuole ottenere anche nel caso pessimo una complessità = O(log n) anziché O(n) , bisogna mantenere l’albero bilanciato.(solo cosi la h può crescere logaritmicamente al crescere di n). COME BILANCIARE L’ALBERO?
di creare nuovi livelli.
modo più uniforme possibile. Un albero binario di ricerca è perfettamente bilanciato o AVL-bilanciato (Adelson – Velsky & Landis) se,per ogni nodo l’altezza del sottoalbero destro con quello sinistro differiscono al massimo di 1. Quindi: log(n + 1) <= h <= 1.44 * log(n + 2) – 0. Tuttavia dopo una rimozione il numero di operazioni da effettuare per ribilanciare l’ albero potrebbe non essere costante).
complessa, ma ha un eccellente tempo di esecuzione nel caso peggiore ed è molto efficiente: effettua ricerche, inserimenti e cancellazioni in un tempo di .. Un albero rosso-nero è un albero binario di ricerca in cui ciascun nodo ha un attributo colore , il cui valore può essere rosso oppure nero. In aggiunta ai requisiti ordinari per un albero binario di ricerca, un albero rosso-nero soddisfa le seguenti proprietà:
Vertice terminale: do(v) = 0; /* non vi sono vertici adiacenti / di(v) > 0 / ma è adiacente a qualcuno */ Vertice iniziale: Do(v) > 0; di(v) = 0; Vertice isolato: d(grado totale) = di(v) + do(v) = 0. GRAFO COMPLETO Se E = {V * V} GRAFO CONNESSO Se per ogni v1, v2€ V esiste almeno un percorso (v1,v2) o (v2,v1) € E GRAFO FORTEMENTE CONNESSO Se per ogni v1, v2€ V esiste un percorso (v1,v2) e (v2,v1) € E COMPONENTE CONNESSA Si dice che G’ = (V’,E’) è componente connessa di G se G’ è un sottografo indotto di G, che è connesso e massimale.
COMPONENTE FORTEMENTE CONNESSA Si dice che G’ ={V’,E’} componente fortemente connessa di G se G’ è un sottografo indotto di G, che è fortemente connesso e massimale. GRAFO INDIRETTO Una coppia (V,E) tale che E è una relazione binaria su V antirifflessiva e simmetrica. L’albero libero è un tipo di grafo indiretto dove valgono anche le proprietà di connettività e aciclicità. GRAFO PESATO Se ad ogni arco del grafo è associato un “peso”(tempo,distanza….). Sarà quindi una tripla G = (V,E,w) Un grafo viene di solito rappresentato tramite una struttura dinamica chiamata lista di adiacenza, formata da una lista primaria contenente i vertici e in ogni vertice è contenuta la testa della lista secondaria relativa a quel vertice contenente gli indirizzi di tutti i vertici adiacenti al vertice in questione. La rappresentazione a matrice di adiacenza spreca in memoria: O(|V| + |E|) Se si ha una matrice di adiacenza invece si mettono in corrispondenza di Ei,j = 1 se esiste un arco tra vi e vj e 0 altrimenti. O(|V^2|) PROBLEMA DELLA VISITA IN AMPIEZZA O IN PROFONDITA’ Per il problema della visita viene introdotto il colore. La visita in ampiezza lavora sullo stesso livello, infatti prima di scendere giù visita pian piano i livelli superiori. Nella visita in profondita viene introdotta anche la variabile tempo. Nella visita in profondità prima si elabora il vertice in cui si è giunti e poi si visitano tutti i vertici ad esso adiacenti e si continua così richiamando l’algoritmo ricorsivamente. PROBLEMA DELL’ ORDINAMENTO TOPOLOGICO. Determinare un ordinamento lineare dei suoi vertici tale che se (v,v’) € E allora v precede v’ nell’ ordinamento. Mette quindi tutti gli eventi in sequenza rispettando i vincoli di dipendenza causale.
Per risolverlo consideriamo l’algoritmo di visita in profondità, considerando i vertici in ordine di tempo decrescente di fine visita. Si crea una lista all’inizio della quale i vertici vengono inseriti man mano che vengono colorati di nero. Poiché gli inserimenti avvengono sempre all’inizio della lista, la complessità è O(|V| + | E|); PER IL PROBLEMA DELLE COMPONENT FORTEMENTE CONNESSE Cioè stablire se Se per ogni v1, v2€ V esiste un percorso (v1,v2) e (v2,v1) € E Si effettuano due visite in profondità: una sul grafo trasposto e una sul grafo dato. La seconda visita in profondità (sul grafo trasposto) avrà bisogno di considerare i vertici in ordine di tepo decrescente di fine prima visita, cioè considerati in ordine topologico. Così facendo gli alberi ricoprenti massimali costruiti durante la seconda visita rappresentano le componenti fortemente connesse. ALBERO RICOPRENTE MINIMO Trovare l’albero contenente tutti i vertici, ma solamente una parte degli archi E. Algoritmo di Kruskal: Parte da tanti alberi liberi quanti sono i vertici del grafo e ad ogni passo esso include l’arco di peso minimo che collega due diversi alberi liberi, fino ad ottenere un singolo albero libero. Algoritmo di Prim: Partendo da un albero libero costituito da un singolo vertice del grafo, ad ogni passo include l’arco di peso minimo che collega un vertice dell’albero libero ad un vertice che non sta ancora nell’albero.Fino ad ottenere un albero libero con tutti i vertici del grafo.
Dati due vertici, uno di partenza e uno di arrivo bisogna calcolare il percorso complessivo di peso minimo che li unisca. Algoritmo di Belman-Ford Si usa una rappresentazione a matrice di adiacenza e consente la presenza di archi di peso negativo e procede alla riduzione sistematica della stima della distanza minima calcolata per ciascun vertice del grafo, sfruttando il fatto che un percorso minimo non può contenere più di |V| - 1 archi (Restituisce 1 se non ci sono cicli di peso negativo, 0 altrimenti). Quindi rilassa tutti gli archi V – 1 volte e man mano aggiorna la stima della distanza minima di ogni vertice dal vertice sorgente.
Algoritmo di Dijkstra T(n) = O(V + E) Si applica solo a grafi privi di archi di peso negativo. Si prende il vertice con la stima della distanza <. Infatti si mette una variabile temporanea della distanza. Dopo aver preso il vertice che ha minima questa variabile si rilassano gli archi da sso uscenti e si ricerca l’arco dal peso minore. A questo punto nell’ultimo vertice puntato dall’ultimo archo durante l’operazione la variabile temporanea verra aggiornata:
A B A V_p
Ecco così trovato il percorso più breve.
Rappresentazione del grafo a matrce di adiacenza. Operazione triangolare su dij: Si aggiorna dij solo se è conveniente, infatti si fa un controllo dove: