Elaborato di Algoritmi e Strutture Dati

Transcript

Elaborato di Algoritmi e Strutture Dati
Elaborato di Algoritmi e Strutture Dati
Trovare una soluzione e analizzarla al problema del Topological-Sort.
2
Indice
Introduzione al Problema
4
Soluzione
5
Visita in profondità
Topological sort
Analisi della complessità
Analisi DFS_Visit
Analisi DFS
Complessità Topological-Sort
Analisi della correttezza
Correttezza della DFS_Visit
Correttezza DFS
Correttezza Topological-Sort
Teorema del cammino bianco
Teorema aciclicità
Dimostrazione Correttezza
5
8
9
9
10
10
11
11
12
13
Esecuzione dell’algoritmo
16
Appendice
23
Bibliografia
27
3
Introduzione al problema
Nella teoria dei grafi, un grafo è definito come:
“Un insieme di elementi detti vertici o nodi che tra di loro sono collegati da archi o lati.
Dando una definizione più formale, si dice grafo una coppia ordinata G = (V, E) di
insiemi, con V insieme dei nodi ed E insieme degli archi, tali che gli elementi di E siano
| |
coppie di elementi di V (da questo segue in particolare | |
“
Nel nostro studio, sono di interesse i Grafi Orientati, definiti come:
“Un insieme D = (V, A), dove V è l'insieme dei vertici di D e A è l'insieme degli archi
orientati di D. Un "arco orientato" è un arco caratterizzato da una direzione. In
particolare, è composto da una "testa" (rappresentata solitamente dalla punta di una
freccia), che si dice raggiunge un vertice in entrata, e una "coda", che lo lascia in
uscita.”
Possiamo ora definire il problema dell’ordinamento topologico:
“Nella teoria dei grafi definisco ordinamento topologico (in inglese topological sort),
un ordinamento lineare di tutti i vertici di un grafo aciclico orientato (directed acyclic
graph). Si dice che i nodi di un grafo sono ordinati topologicamente se essi sono
disposti in modo tale che ogni nodo viene prima di tutti i nodi collegati ai suoi archi
uscenti. L'ordinamento topologico non è un ordinamento totale, poiché la soluzione
può non essere unica. Nel caso peggiore infatti si possono avere n! ordinamenti
topologici diversi che corrispondono a tutte le possibili permutazioni degli n nodi.”
Ovviamente non ha senso definire il problema su grafi non aciclici, in quanto è impossibile trovare
un ordinamento topologico come descritto sopra.
4
Soluzione
In maniera differente all’algoritmo precedente, presenteremo una soluzione a questo problema,
ma soffermandoci in maniera precisa ad analizzare ogni sua parte, in termini di correttezza e
complessità.
Un ordinamento topologico di un grafo aciclico orientato G si può basare sulla visita in profondità
del grafo: per ciascuno nodo viene registrato il tempo di inizio e fine visita, un ordinamento può
essere ottenuto ordinato i nodi in base al tempo di fine visita.
A. Visita in profondità:
Una visita in profondità consiste nel visitare il grafo sempre più “in profondità” se possibile. In
questo tipo di visita, si ispezionano gli archi dall’ultimo vertice scoperto v che ha ancora archi non
ispezionati che escono da esso. Quando tutti gli archi di v sono stati ispezionati, si torna indietro
ad analizzare gli archi che escono dal vertice dal quale v era stato scoperto. Questo processo
continua finché non saranno stati scoperti tutti i vertici che sono raggiungibile dal vertice sorgente
iniziale. Se restano dei vertici non scoperti, uno di essi viene selezionato come nuovo vertice
sorgente e ricomincia la visita. Si continua in questo modo finché tutti i vertici non saranno
scoperti.
Quando un vertice v viene scoperto durante l’ispezione della lista di adiacenza del vertice u già
scoperto, si registra questo evento assegnando u all’attributo v.predecessore (v.π) di v.
Il sotto-grafo dei predecessore, definito come Gπ (V, Eπ) dove
Eπ = (
}
Gπ forma una foresta DF composta da alberi Df. Gli archi di Eπ sono detti archi dell’albero.
Durante la fase di scoperta, i vertici vengono colorati. Inizialmente sono bianchi, diventano grigi
quando vengono scoperti durante la visita e, infine, vengono colorati di nero quando vengono
completati.
5
Oltre a creare una foresta DF, la visita in profondità associa anche a ciascun vertice delle
informazioni temporali. Ad ogni vertice viene associata una informazione sul momento di scoperta
e sul momento di completamento. Questo informazioni temporali potranno essere usate per
l’ordinamento topologico. Ovviamente per ogni vertice v sarà verificata la condizione
u.tempoScoperta < u.tempo.completamento.
Mostriamo il pseudo codice della procedure DFS
Visita in profondità DFS
DFS()
1
for Ogni vertice u appartenente a G
2
u.color = white
3
u.pred = NULL
4
time = 0;
5
for Ogni vertice u appartenente a G
6
if u.color == Color.White
7
DFS_Visit(G,u);
La DFS-VISIT, invece, è la seguente procedura
DFS-VISIT
DFS_VISIT(G,u)
1
time = time + 1;
2
u.tempoScoperta = time;
3
u.color = Gray;
4
for Ogni vertice v appartenente a G.Adj[u]
5
if v.color == Color.White
6
DFS_Visit(G,u)
7
u.color = Black;
8
u.tempoCompletamento = time = time + 1
La figura successiva, presa dal Cormen, illustra le varia fasi della procedura DFS applicata al grafo
illustrato prima figura.
6
La figura mostra che dopo che gli archi sono stati ispezionati dall’algoritmo, vengono rappresentati
su uno sfondo grigio ( se sono archi dell’albero) o tratteggiati (negli altri casi). Quelli che non sono
archi dell’albero sono etichettati con le lettere A, B o F a seconda che siano archi all’indietro,
trasversali o in avanti (che definiremo specificatamente in seguito). All’interno dei vertici sono
presenti le informazioni temporali.
La procedura DFS opera nella maniera seguente. Le prime 3 righe inizializzano i vertici, cioè
attribuiscono a tutti vertici il colore bianco e settano a NULL l’attributo pred( π ). La riga 4 azzera il
contatore globale, time. Le successive righe controllano, uno alla volta, tutti i vertici del grafo G e,
quando ne trovano uno di colore bianco, lo vistano chiamando la procedura DFS_Visit su quel
vertice. Questo vertice diventa la radice di un nuovo albero della foresta DF.
7
B. Topological sort
Avendo definito le funzione, possiamo correttamente mostrare il TOPOLOGICAL-SORT, l’algoritmo
fa uso della procedura DFS mostrare precedentemente.
Ordinamento Toplogico
Topological-sort(G)
1
Chiama DFS(G) per calcolare i tempi di completamente per ciascun vertice v
2
Una volta completata l'ispezione di un vertice, inserisce il vertice in testa ad un vettore.
3
return il vettore dei vertici
8
Analisi della complessita
Iniziamo calcolando la complessità della procedura più interne, la DFS-Visit. Per farlo utilizzeremo
l’analisi ammortizzata, precisamente il metodo delle aggregazioni.
Sostanzialmente, nell'analisi ammortizzata, per tempo di esecuzione di una sequenza di
operazioni, si intende la media dei costi di tali operazioni. È però importante fare attenzione a non
confondersi con l'analisi del costo medio in cui si fa riferimento a concetti di teoria della
probabilità per valutare il costo di esecuzione sull'istanza media.
Esistono diverse tecniche usate per l'analisi ammortizzata:

Metodo dell’aggregazione:
Viene determinato un limite superiore del costo totale T(n) di una sequenza di operazioni
per poi definire il costo ammortizzato come: T(n)/n dove n è il numero di operazioni della
sequenza.

Metodo degli accantonamenti:
consiste nel definire un “debito” inteso come differenza tra il costo reale dell’operazione e
il costo attribuito all'operazione. Il debito viene poi saldato definendo più alto il costo
attribuito ad altre operazioni facendo però attenzione a far quadrare il bilancio. Alla
“chiusura” è ammesso un bilancio negativo, in modo tale che ogni debito sia stato pagato.
Se questo vincolo è stato rispettato, resta legittimato il ragionamento in termini di costi
attribuiti anziché di costi reali.

Metodo del potenziale:
è una tecnica molto simile al Metodo degli accantonamenti in cui però il “credito”
accumulato è direttamente associato alla struttura di dati e tale “credito” è visto come
“l'energia potenziale” della struttura stessa.
A. Analisi DFS_Visit
Useremo l’analisi ammortizzata per valutare la complessità dell’Algoritmo.
Supponiamo inizialmente di non voler usare l’analisi ammortizzata, la riga 4 mostra un ciclo for,
nel caso peggiore la lista di adiacenza ha E elementi. Dunque in ciclo for compio un O(E) confronti.
9
Inoltre, nel caos peggiore, devo suppore che tutti i vertici siano bianchi e ciò mi comporta che
avviene sempre la chiamate ricorsiva. Se scrivessi l’equazione ricorrente e la risolvessi otterrei un
limite superiore veramente elevato.
Usando invece il metodo dell’analisi ammortizzata, notiamo che, la procedura DFS-VISIT è
chiamata esattamente una volta per ogni vertice v appartenete a V, poiché la DFS-Visit viene
invocata soltanto se un vertice è bianco e la prima cosa che fa è colorare il vertice. Durante un
esecuzione di DFS-Visit(v), il ciclo nelle righe 4-7 viene eseguito Adj[v] volte.
Poiché:
∑
= (E)
Il costo totale per eseguire le righe 4-7 d DFS_visit è teta
(E).
B. Analisi DFS.
Continuando ad analizzare la complessità con il metodo dell’analisi ammortizzata, possiamo
vedere che i cicli alle righe 1-3 e 5-7 della DFS, impiegano un tempo (V), escluso il tempo per
eseguire la DFS-VISIT. Utilizzando il risultato sulla DFS-Visit, il risultato della complessità è (V + E).
C. Complessità topological sort
La versione del topological-sort implementata inserisce in testa ad un vettore il vertice una volta
che è colorato di nero. Questa operazione aggiunta ha un costo (1) e viene aggiunta alla DFSVisit non fa cambiare la complessità della funzione, ergo la complessità del topological sort è la
complessità della DFS che è (V + E).
10
Analisi della correttezza
In questo paragrafo dimostreremo la correttezza della procedura di ordinamento topologico.
A. Correttezza della DFS-Visit
Dimostriamo prima la correttezza della DFS-Visit.
Supponiamo, senza perdere di generalità, che il grafo sottoposto alla procedura sia connesso. Il
grafo sarà completamente esplorato e dunque i suoi nodi tutti visitati, quando tutti i vertici sono
colorati di nero.
Un grafo è detto connesso se, per ogni coppia di vertici (u, v)
a v.
V, esiste un cammino che collega u
Essendo la procedura ricorsiva dimostriamo la correttezza usando il principio di induzione
completa.
Passo Base: Verifichiamo che la chiamata è corretta quando il grafo è composto da un solo
elemento, u. Allora la lista ADJ di u è vuota quindi che non entro nel ciclo for e quindi il
nodo viene colorato di nero . In questo caso u viene colorato di nero, essendo u l’unico
vertice, concludo che tutti i nodi sono neri e dunque la procedura è corretta nel caso base.
Passo ricorsivo: Supponiamo che le chiamate ricorsive siano corrette, quindi che la
chiamata su un sotto-grafo sorgente v, raggiunge tutto i vertici connessi ad esso.
Passo conclusivo: Al passo conclusivo per ogni nodo della lista di adiacenza di u non bianco
viene chiamata ricorsivamente la DFS_visit. Ciò vuol dire che tutti i nodi raggiungibili dalla
lista di adiacenza di u sono correttamente raggiunti. Questo, perché, essendo il grafo
connesso tutti i nodi sono raggiungibile e lo sono o perché adiacenti direttamente ad u
oppure perché sono raggiungibile attraverso un elemento della lista di adiacenza di u.
Quindi chiamando correttamente la DFS_visit su tutti i nodi bianchi adiacenti a u (quelli
non bianchi che sono già stati visitati), ho la certezza di aver visitato tutto l’albero. Allora
coloro u di nero. E concludo che tutti i vertici sono neri, ergo il grafo è correttamente
scoperto.
11
B. Correttezza DFS
Uso l’invariante di ciclo per valutare la correttezza del ciclo for. L’invariante di ciclo che utilizzo è il
seguente:
“All’ i-esimo passo del for avrò un insieme di vertici analizzati di colore nero e due insieme
si vertici non analizzati uno di colore bianco e l’altro nero”.
Inizializzazione: Non ho ancora analizzato nessun vertici e sono tutti di coloro bianco.
Conservazione: Prima’ i-esima iterazione del ciclo avrò chiamato su k vertici la DFS-Visit,
questa procedura colora di nero i vertici su cui è chiamata e se questi vertici fanno parte di
un grafo connesso, colora di nero anche tutti i vertici di questo albero. Avrò quindi un
insieme di nodi neri che è corrispondente ai vertici analizzati, un altro di nodi neri che sono
quelli connessi a quelli analizzati e un insieme di nodi bianchi che sono quelli che fanno
parte di sotto-grafi non connessi a quelli scoperti. L’i-esimo vertice se è nero, vuol dire che
è stato già visitato, altrimenti è bianco e ne consegue che lo visiterò ed alla fine sarà
colorato di nero.
Conclusivo: Alla passo conclusivo ho analizzato tutti i vertici di V. Quindi sono
correttamente tutti colorati di nero.
È presenta l’ovvia ottimizzazione che non permette la chiamata alla DFS-Visit nel caso in cui un
nodo è stato già scoperto perché appartenente al grafo connesso di un vertice prima analizzato. La
chiamata DFS-Visit, su questo nodo non aggiungerebbe niente di nuovo in quanto è già avvanuta
in precedenza.
12
C. Correttezza topological sort
Prima di valutare la correttezza dell’algoritmo, diamo le seguente definizioni per la classificazione
degli archi:
Sia Gπ la foresta DF generata da DFS sul grafo G, definisco:
• Arco d’albero: Gli elementi dell’insieme Eπ sopra definito.
• Arco di ritorno: gli archi (u, v) che connettono un vertice u con un antenato v nell’albero
DF.
• Arco in avanti: archi (u, v) non appartenenti all’albero DF che connettono l’arco u con un
discendente v
• Arco di attraversamento: tutti gli altri archi. Possono connettere vertici nello stesso
albero DF (a patto che un vertice non sia antenato dell’altro nell’albero) o vertici in alberi
DF differenti.
L’algoritmo DFS può essere usato per classificare gli archi di un grafo G. Per farlo si utilizza il colore
del vertice che si raggiunge durante la visita dell’arco (u, v):
• se v è bianco: allora l’arco è un arco d’albero
• se v è grigio: allora l’arco è un arco di ritorno
• se v è nero: allora l’arco è un arco in avanti o un arco di attraversamento
• se inoltre d[u] < d[v] allora è un arco in avanti
•
se d[v] < d[u] allora è un arco di attraversamento
Per dimostrare la correttezza del topological sort, vanno dimostrati i seguenti teoremi.
13
Teorema del cammino bianco:
“In una foresta di un grafo G = (V,E), il vertice v è un discendente del vertice u se e soltanto
se, al tempo u.tempoScoperta in cui viene scoperto u, il vertice v può essere raggiunto da u
lungo un cammino che è formato esclusivamente da vertici bianchi”.
Dimostrazione:
=> se v = u, allora il cammino da u a v contiene soltanto il vertice u, che è ancora bianco quando
impostiamo il valore di u.tempoScoperta. Adesso, supponiamo che v sia un discendente proprio di
u nella foresta DF allora u.tempoScoperta < v.tempoScoperta, e quindi v è bianco al tempo
u.tempoScopera. Poiché v può essere un discendente qualsiasi di u, tutti i vertici lungo l’unico
cammino semplice da u a v nella foresta DF sono bianchi al tempo u.tempoScoperta (Vale facendo
questo discorso per tutti i vertici tra v e u) .
<= Supponiamo che si sia un cammino di vertici bianchi da u a v al tempo u.tempoScoperta e che v
non diventi un discendente di u nell’albero DF. Senza perdere id generalità, supponiamo che tutti
gli altri vertici diversi da v lungo il cammino diventino discendenti di u( altrimenti, indichiamo con v
il vertice più vicino u lungo il cammino che non diventa discendente di u). Sia w il predecessore di
v lungo il cammino, cosicché w è un discendente di u( in effetti w e u potrebbero essere lo stesso
vertice) e si ha che w.tempoFinale <= u.tempoFinale. Poiché u.TemoScoperta < v.tempoScoperta <
w.tempoFinale <= u.tmpoFinale. Il teorema delle parentesi implica che l’intervallo
[v.tempoScoperta, v.tempoFinale] sia interamente contenuto nell’intervallo [u.tempoScoperta,
u.tempoFinale]. Dunque v deve essere un discendente di u.
Teorema:
“Un grafo orientato è aciclico se e solo se l’algoritmo DFS su G non trova alcun arco di
ritorno”.
Dimostrazione:
=> Supponiamo che G contenga un ciclo c, allora la DFS necessariamente troverà un arco di
ritorno. Infatti, sia v è il primo vertice che viene scoperto in c, e (u,v) l’arco che lo precede in c.
Allora, al tempo v.tempoScoperta, c’è un percorso bianco da v a u e, per il teorema del percorso
bianco, sappiamo che u diventa un discendente di v nella foresta DF. Perciò, (u,v) deve essere un
arco di ritorno
14
<= Supponiamo che DFS incontri un arco di ritorno (u,v). Allora il vertice v è un antenato di u nella
foresta DF. Quindi esiste certamente un percorso che va da v a u nel grafo G. Tale percorso,
concatenato con l’arco di ritorno (u,v), forma un ciclo, quindi il grafo G non è aciclico.
Dimostrati i teoremi precedenti, possiamo dimostrare che la procedura Topological-Sort mostrata
è corretta nel caso in cui G sia aciclico.
Dimostrazione correttezza topological sort:
Dobbiamo dimostrare che vale la proprietà di ordinamento topologico: per ogni arco (u, v) E, u
precede v nell’ordinamento. Ma questo equivale a dimostrare che, dopo la DFS, per ogni coppia di
vertici u e v, se abbiamo che (u, v) E, allora f[v]<f[u]. In tal caso abbiamo appunto che u
precederà v nell’ordinamento (vedi Algoritmo). Dimostriamo che, dopo DFS, per ogni coppia di
vertici u e v, se (u, v) E, allora f[v]<f[u]. Preso un qualsiasi arco (u, v) E esplorato da DFS,
quando l’arco viene esplorato, v non può essere grigio, altrimenti v sarebbe un antenato di u e (u,
v) un arco di ritorno, contraddicendo il teorema precedente. Quindi v o è bianco o è nero.
il vertice v è o bianco o nero.
a) Se v è bianco, allora diventa un discendente di u è f[v]<f[u] )
b) Se v è nero, allora ovviamente sarà f[v]<f[u].
In conclusione, dato l’ordine di inserimento nella lista e l’aciclicità di G, segue la correttezza.
15
Esecuzione dell’algoritmo
Le funzione Topological-Sort sopra descritta è stata implementata ed è stato misurato il tempo di
esecuzione dell’ algoritmo al crescere di n. Mostreremo adesso i risultati, commentandoli.
Il linguaggio scelto per implementare gli algoritmi è stato il linguaggio java, “compilato”
nell’ambiente “Eclipse”, la scelta è dovuta solo alla maggior confidenza personale nei confronti di
questo linguaggio e nei confronti dell’ambiente.
Sono state implementate tra classi:

Una classe Vertice che contiene gli attributi privati:
o Key , che contiene il valore associato al vertice;
o Adj, è la Lista di adiacenza, implementata attraverso la classe List di java.util;
o Color, che contiene il color del vertice;
o time_scoperta, il tempo in cui il vertice viene scoperto durante la visita;
o time_finale, il tempo in cui è stata completata la visita del vertice;
o pred, he contiene il predecessore al vertice nell’albero BF
o Un costruttore che prende in input il valore di chiave ed inizializza un vertice con
quel valore di chiave, crea la lista di adiacenza, setta a zero tempo_finale e
tempo_scoperta, setta a bianco il colore del vertice e setta a NULL il campo pred.
public class Vertice {
private int key;
private List<Vertice> Adj;
private int color;
private int time_scoperta;
private int time_finale;
private Vertice pred;
public Vertice(int key) {
super();
this.key = key;
Adj = new ArrayList<Vertice>();
this.color = Color.White;
this.time_scoperta = this.time_finale = 0;
pred = null;
16

Un interfaccia Color che mette a disposizione i colori per i vertici: Bianco, Grigio e Nero
public interface Color {
public static int White=0;
public static int Gray=1;
public static int Black=2;

Una classe Grafo che contiene gli attributi private:
o V, che è un vettore che rappresenta l’insieme dei vertici del grafo;
o Time, il valore temporale utilizzato per settare i tempi nei vertici durante la visita;
private Vector<Vertice> V;
private int time;
Inoltre contiene i metodi private DFS e DFS_Visit e il metodo private Topological-Sort (che
rispecchiano fedelmente il psudo-codice scritto nel capitolo precedente, è comunque
possibile visualizzare il codice in appendice)
È inoltre fornito un costruttore che prende in ingresso un intero n e costruisce un grafo di n
vertici ed n+ (n/5) -1 archi. Il costruttore è stato implementato in modo tale che costruisce
quasi sempre un grafo aciclico. Il grafo viene costruito in questa maniera :
o Viene creato un vettore di n vertice e per semplicità si associa al valore key di ogni
vertice un valore crescente che parte da 0.
o Questo vettore lo si vede come la linearizzazione di un albero binario completo,
tranne l’ultimo elemento
o Si creano dunque le liste di adiacenza seguendo la regola che i figli del nodo di
posto i nel vettore sono quelli di posto 2i +1 e 2i+2 (nell’ipotesi che i parta da 0).
o In maniera casuale si creano n/5 archi in avanti o trasversi. Può capitare che si crea
un arco tra un nodo e se stesso. In questo caso si è in presenza di un nodo
all’indietro. Invece di eliminare questa situazione, la si è comunque lasciata per far
vedere che l’algoritmo è in grado di gestire l’eccezione “Presenza di arco
all’indietro”. L’aggiunta di un arco già presenta( cosa possibile perché gli ultimi n/5
sono casuali) viene proibita.
In seguito si può vedere il codice:
17
public Grafo(int n){
V = new Vector<Vertice> (); // crea vettore che conterrà i Vertex
time = 0; // inizializza il tempo di ricerca
int [] V_temp = new int[n+1];
for( int i = 0; i<n;i++){ // crea n vertici e li inserisce nel vettore
V_temp[i] = i;
Vertice v = new Vertice(i);
V.add(v);
}
//costruisco per semplicità il grafo come albero binario
for(int i = 0; i<n/2;i++){
V.get(i).setAdj(V.get(2*i+1) );
if(2*i+2 < n) V.get(i).setAdj(V.get(2*i +2) );
}
// Genero casualmente archi in avanti e trasversali .
int j_1 , j_2;
for(int k = 0; k < n/5;k++){
j_1 = (int) (Math.random() * 100*n) % (n/2);
j_2 = j_1 + (int) (Math.random() *100*n) % (n/2);
//System.out.println("Creao arco tra: "+j_1+" e :"+j_2);
V.get(j_1).setAdj(V.get(j_2));
}
Nel caso in cui il grafo è ciclico vuol dire durante la fase si scoperta della DFS_Visit viene
analizzato un nodo di colore grigio, ciò vuol dire che è presente un arco all’indietro ergo il
grafo è ciclico, quando si verifica questo caso, l’algoritmo riconosce l’arco all’indietro e
lancia un eccezione.
Descritti questi dettagli di programmazione andiamo a valutare la correttezza dell’algoritmo con
un input di 15 elementi:
Mostriamo lo screenshot relativo alla costruzione del grafo partendo dall’albero binario:
18
Il grafo creato è il seguente:
L’arco tra 0 e 2 non è stato creato in quando già esisteva.
L’ordinamento topologico proposto come risultato dell’algoritmo è il seguente(anche se può non
sembrare il risultato è uno screenshot):
Che graficamente sarebbe:
19
Che come si vede non ha archi all’indietro, è correttamente un ordinamento topologico.
Verifichiamo adesso il tempo di calcolo delle funzione. Per valutare questo tempo, misuriamo il
tempo di sistema in naso secondi (ns), prima e dopo l’esecuzione della funzione. Sottraendo il
tempo finale a quello iniziale, abbiamo una buona stima del tempo impiegato dalla funzione per
svolgere le operazione.
Visto che siamo interessati a valutare l’andamento della funzione all’aumentare di V + E. Essendo
nel nostre esempio di dimensioni comparabile la ocmplessità TETA( E + V) = TETA(E) = TETA(V).
Per noi l’aumentare di n è uguale ad un aumento lineare sia degli arci che dei vertici.
Valuteremo il tempo di esecuzione all’aumentare del numero di input n in modo
esponenzialmente come potenza di 2, fino a 2^16 (Con questo input abbiamo tempi di esevuzione
accettabili) e valuteremo come varia conseguentemente il tempo di esecuzione. Per far ciò, ad
ogni esecuzione della funzione salviamo in un vettore i risultati del tempo, alla fine del programma
scriviamo questi valori su un file di testo. È stato poi scritta una macro in Excel che legge questi file
di testo e costruisce conseguentemente i grafi che indicano l’andamento dei tempo di esecuzione
(sull’asse y) al variare dell’input.
Mostreremo l’andamento della funzione su un grafo, dove sull’asse delle ascisse ci sarà la
dimensione del vettore, mentre sull’asse delle ordinata ci sarà il tempo di esecuzione in ns. Per
ogni funzione mostreremo due grafi, tra di loro differiscono solo per la scale dell’asse x. Il grafo di
sx presenta una scala lineare, mentre quello di dx una scala esponenziale. Ovviamente le funzione
sotto sottoposte allo stesso vettori di input.
20
Andamento della funzione:
Analizzando i dati possiamo riscontrare per piccoli valori dell’input il tempo di esecuzione varia quasi esponenzialmente, ma per numero di input
elevati l’andamento cresce linearmente. Questa osservazioni va a cappello con quanto detto sull’analisi asintotica in quanto essa è valida per un certe
n >= n0.
La costante c1 è 0,1.
21
Quello che possiamo notare confrontano l’esecuzione di questo algoritmo con quello precedente,
ad esempio con il brute-force quadratico, possiamo osservare che anche se in quel caso
l’andamento della funzione era quadratico, i tempo di esecuzione della funzione erano minore.
Questo lo possiamo spiegare osservando che in questo algoritmo abbiamo dei coefficienti
moltiplicativi molto elevati.
22
Appendice
Classe Grafo:
package topological_sort;
import
import
import
import
java.io.FileNotFoundException;
java.io.PrintWriter;
java.util.Stack;
java.util.Vector;
public class Grafo {
private Vector<Vertice> V;
private int time;//serve per un esplicito controllo di Dag.
public Grafo(int n){
V = new Vector<Vertice> ();
time = 0;
int [] V_temp = new int[n+1];
for( int i = 0; i<n;i++){
//System.out.println(i);
V_temp[i] = i;
Vertice v = new Vertice(i);
V.add(v);
}
//costruisco per semplicità il grafo come albero binario
for(int i = 0; i<n/2;i++){
V.get(i).setAdj(V.get(2*i+1) );
if(2*i+2 < n) V.get(i).setAdj(V.get(2*i +2) );
}
// Genero casualmente archi in avanti e trasversali da aggiungere al
grafo.
int j_1 , j_2;
for(int k = 0; k < n/5;k++){
j_1 = (int) (Math.random() * 100*n) % (n/2);
j_2 = j_1 + (int)
(Math.random() *100*n) % (n/2);
//System.out.println("Creao arco tra: "+j_1+" e :"+j_2);
V.get(j_1).setAdj(V.get(j_2));
}
}
public Vector<Vertice> DFS() throws Throwable{
Vector<Vertice> Tp = new Vector<Vertice>();
for (int i= 0; i<V.size();i++)
if( (V.get(i).getColor() == Color.White)){
23
DFS_Visit(V.get(i),Tp);
//System.out.println("\nDFS vertice "+i);
}
return Tp;
}
private void DFS_Visit(Vertice u, Vector<Vertice> s) throws Throwable{
u.setColor(Color.Gray);
u.setTimeScoperta(++time);
for(int i = 0; i < u.getAdjSize(); i++){
if( (u.getAdj(i).getColor() == Color.Gray) ) throw new
Throwable("grafo ciclico");// grafo aciclico lancia eccezione
else if ((u.getAdj(i).getColor() == Color.White)){
u.getAdj(i).setPred(u);
//System.out.print("
DFS_visit vertice :"
+u.getAdj(i).getKey() );
DFS_Visit(u.getAdj(i),s);
}
}
u.setColor(Color.Black);
u.setTimeFinale(++time);
//System.out.println(u.getKey()+" \n\n\n");
s.add(0,u);
}
public Vertice getV(int i) {
return V.get(i);
}
public void setV(Vertice v) {
V.add(v);
}
public static void main(String args[]) throws FileNotFoundException{
int n = 1;
long secondi_start, secondi_stop;
long tempo [] = new long[18];
for(int i = 0; i<17;i++){
n = 2*n;
Grafo g = new Grafo(n);
try {
secondi_start = System.nanoTime();
Vector<Vertice> s = g.DFS();
secondi_stop = System.nanoTime();
tempo[i] = (secondi_stop - secondi_start);
//System.out.println(s);
} catch (Throwable e){
e.printStackTrace();
}
}
PrintWriter out3 = new PrintWriter("tempo_esecuzione2.txt");
for( int i = 0; i <17; i++)
out3.print((int) tempo[i]+" ");
out3.close();
System.out.println("fine");
24
}}
.
Classe Vertice:
package topological_sort;
import java.util.ArrayList;
import java.util.List;
public class Vertice {
private int key;
private List<Vertice> Adj;
private int color;
private int time_scoperta;
private int time_finale;
private Vertice pred;
//private Vertice succ;
// forse nel topological sort non serve
public Vertice(int key) {
super();
this.key = key;
Adj = new ArrayList<Vertice>();
this.color = Color.White;
this.time_scoperta = this.time_finale = 0;
pred = null;
//succ = null;
}
public int getKey() {
return key;
}
public void setKey(int key) {
this.key = key;
}
public Vertice getAdj(int i) {
return Adj.get(i);
}
public void setAdj(Vertice v) {
if(!Adj.contains(v) || v == this)
Adj.add(0, v); // insert in testa
//else System.out.println("non creato");
}
public int getColor() {
return color;
}
public void setColor(int color) {
this.color = color;
}
public int getTimeFinale() {
return time_finale;
}
25
public void setTimeFinale(int time) {
this.time_finale = time;
}
public int getTimeScoperta() {
return time_scoperta;
}
public void setTimeScoperta(int time) {
this.time_scoperta = time;
}
public Vertice getPred() {
return pred;
}
public void setPred(Vertice pred) {
this.pred = pred;
}
/*public Vertice getSucc() {
return succ;
}
public void setSucc(Vertice succ) {
this.succ = succ;
}*/
public int getAdjSize() {
return Adj.size();
}
public String toString(){
return new String(String.valueOf(this.key));
}
}
26
Bibliografia
[1] Thomas . Cormen, Charles E. Leiserson, Ronald L. Rivest, Clifford Stein; Introduction to
Algorithms third edition; 2009.
[2] http://en.wikipedia.org/wiki/Topological_sorting; Wikipedia.
[3] http://en.wikipedia.org/wiki/Graph_(mathematics); Wikipedia.
27