Indice
Indice .................................................................................................................................................... 2
Introduzione ......................................................................................................................................... 3
Capitolo 1: Bucket-Sort ....................................................................................................................... 4
1.1 Intro ............................................................................................................................................ 4
1.2 Codice ........................................................................................................................................ 5
1.2.1 File Header (.h) ................................................................................................................... 5
1.2.2 BucketSort (.cpp) ................................................................................................................ 6
1.2.3 Main (.cpp) .......................................................................................................................... 8
1.3 Grafici e Tempi di esecuzione ................................................................................................. 10
1.3.1 CASO MEDIO .................................................................................................................. 10
1.3.1 CASO PEGGIORE ........................................................................................................... 13
Capitolo 2: Heap-Sort ........................................................................................................................ 16
2.1 Intro .......................................................................................................................................... 16
2.1 Codice ...................................................................................................................................... 17
2.1.1 File Header (.h) ................................................................................................................. 17
2.1.2 HeapSort (.cpp) ................................................................................................................. 18
2.2 Grafici e Tempi di esecuzione ................................................................................................. 25
2.2.1 Complessit n*log(n) ........................................................................................................ 25
2.2.2 Confronto tra due versioni di Build Max Heap................................................................. 27
2.2.3 Heap Extract Max su coda di max-priority ....................................................................... 28
Capitolo 3: Strongly-Connected-Components ................................................................................... 29
3.1 Intro .......................................................................................................................................... 29
3.2 Codice ...................................................................................................................................... 30
3.2.1 File Header (.h) ................................................................................................................. 30
3.2.2 SSC (.cpp) ......................................................................................................................... 31
3.2.3 Main (.cpp) ........................................................................................................................ 36
3.3 Tempi di esecuzione................................................................................................................. 37
Introduzione
In questo elaborato sono discusse le principali caratteristiche di tre particolari
algoritmi (Bucket-Sort, Heap-Sort e Strongly-Connected-Components), di cui i
primi due appartengono alla categoria degli algoritmi di ordinamento, mentre il terzo
a quella delle ricerche su grafi.
Di questi algoritmi, dopo una breve introduzione, viene riportato il codice
implementato in C++ e i tempi di esecuzione sotto forma di tabelle e grafici realizzati
in Excel, al fine di poterli opportunamente confrontare con quelli teorici ottenuti dall'
analisi asintotica.
A tale scopo, stato utilizzato Eclipse IDE Luna for C++ Developers come ambiente
di sviluppo.
Capitolo 1: Bucket-Sort
1.1 Intro
Il Bucket Sort un particolare algoritmo di ordinamento che lavora sul posto
su di un input generato da un processo casuale che distribuisce gli elementi
uniformemente e indipendentemente nellintervallo [0,1) e che ha tempo lineare di
esecuzione pari a (n).
Partendo da un array di input di n elementi, il codice richiede che ci sia un
vettore ausiliario di liste concatenate, detto bucket, su cui viene eseguito
lordinamento. Infine necessario concatenare le varie liste ordinate per realizzare
lordinamento complessivo dei valori dellinput.
Bucket Sort necessita dellalgoritmo di ordinamento Insertion Sort per ordinare
gli elementi di ciascuna lista. In particolare, poich noto che la complessit nel caso
peggiore di Insertion Sort dellordine di O(n^2), il tempo di esecuzione nel caso
peggiore del BS pari a:
T(n) =
Tuttavia possibile calcolare il valore atteso del tempo di esecuzione di Bucket Sort
che, quindi, nel caso medio risulta uguale a (n).
1.2 Codice
Di seguito riportato il codice in C++ organizzato secondo la
programmazione a moduli che prevede la creazione di un progetto contenente, oltre
al programma principale di esecuzione dellalgoritmo (main.ccp), anche il modulo
relativo alle diverse funzioni richiamate dallo stesso (bucketsort.cpp) e una libreria
che le include (lib.h).
<cstdlib>
<iostream>
<vector>
<math.h>
<stdlib.h>
typedef struct ns {
float data;
struct ns *next;
} node;
void bucket_sort (float A[], int, int); // ordinamento secondo Bucket Sort
node* insertion_sort (node *);
// ordinamento secondo Insertion Sort
void printBuckets(node *);
// stampa dei valori presenti nei buckets
void print(float A[], int ); // stampa vettore di input ordinato
#endif /* LIB_H_ */
<cstdlib>
<iostream>
<stdlib.h>
"lib.h"
}*/
for (int z=0; z<dim_B; z++)
{
B[z]=insertion_sort(B[z]);
}
int giro = 0;
for (int h=0; h<dim_B; h++)
{
while(B[h]!=NULL)
{
A[giro]=B[h]->data;
B[h]=B[h]->next;
giro++;
}
}
}
<cstdlib>
<stdio.h>
<iostream>
<time.h>
"lib.h"
// Bucket B di 10 posizioni!
t = clock ();
for (int l = 0; l<100; l++)
{
for (int i=0; i<(sizeof(A)/sizeof(float)); i++)
{
A[i] = static_cast<float>(rand())/static_cast<float>(RAND_MAX);
}
bucket_sort(A,(sizeof(A)/sizeof(float)), dim_B);
}
t = clock () - t;
cout <<"\n";
printf("%10.9f\n",(((double)(t))/CLOCKS_PER_SEC));
/*cout<<(sizeof(A)/sizeof(float))<<"\n";
cout << "Initial array: " << endl;
print(A,(sizeof(A)/sizeof(float)));
cout << "-------------" << endl;
cout << "-------------" << endl;
cout << "Sorted array: " << endl;
print(A, (sizeof(A)/sizeof(float)));
*/
//system("PAUSE");
return EXIT_SUCCESS;
}
Tabella:
n
10
20
30
40
50
60
70
80
90
100
125
150
175
200
250
300
350
400
450
500
600
700
800
900
1000
1100
1200
1300
1400
1500
1600
bucketsort
c2
c1
0,0000281
1,31579E-05
0,0000313
2,63158E-05
0,0000609
3,94737E-05
0,0001107
5,26316E-05
0,0000797
6,57895E-05
0,0001068
7,89474E-05
0,0001318
9,21053E-05
0,0001449
0,000105263
0,0001594
0,000118421
0,0001062
0,000131579
0,0001032
0,000164474
0,0002097
0,000197368
0,0002813
0,000230263
0,000225
0,000263158
0,000437
0,000328947
0,000562
0,000394737
0,0005
0,000460526
0,000437
0,000526316
0,000923
0,000592105
0,001235
0,000657895
0,001263
0,000789474
0,001752
0,000921053
0,001453
0,001052632
0,001094
0,001184211
0,0027
0,001315789
0,002622
0,001447368
0,003391
0,001578947
0,003044
0,001710526
0,003262
0,001842105
0,00469
0,001973684
0,00484
0,002105263
n (caso medio)
5,26316E-05
0,000105263
0,000157895
0,000210526
0,000263158
0,000315789
0,000368421
0,000421053
0,000473684
0,000526316
0,000657895
0,000789474
0,000921053
0,001052632
0,001315789
0,001578947
0,001842105
0,002105263
0,002368421
0,002631579
0,003157895
0,003684211
0,004210526
0,004736842
0,005263158
0,005789474
0,006315789
0,006842105
0,007368421
0,007894737
0,008421053
10
20
30
40
50
60
70
80
90
100
125
150
175
200
250
300
350
400
450
500
600
700
800
900
1000
1100
1200
1300
1400
1500
1600
1700
1800
1900
2000
3000
4000
5000
6000
7000
8000
9000
10000
15000
20000
25000
30000
35000
40000
45000
50000
60000
70000
80000
90000
100000
150000
0,00734
0,00797
0,00953
0,0103
0,01469
0,00797
0,01103
0,01328
0,01062
0,0202
0,01375
0,0313
0,031
0,11
0,062
0,218
0,11
0,391
0,422
0,548
0,515
1,058
1,288
1,721
1,946
3,367
0,002236842
0,002368421
0,0025
0,002631579
0,003947368
0,005263158
0,006578947
0,007894737
0,009210526
0,010526316
0,011842105
0,013157895
0,019736842
0,026315789
0,032894737
0,039473684
0,046052632
0,052631579
0,059210526
0,065789474
0,078947368
0,092105263
0,105263158
0,118421053
0,131578947
0,197368421
0,008947368
0,009473684
0,01
0,010526316
0,015789474
0,021052632
0,026315789
0,031578947
0,036842105
0,042105263
0,047368421
0,052631579
0,078947368
0,105263158
0,131578947
0,157894737
0,184210526
0,210526316
0,236842105
0,263157895
0,315789474
0,368421053
0,421052632
0,473684211
0,526315789
0,789473684
1700
1800
1900
2000
3000
4000
5000
6000
7000
8000
9000
10000
15000
20000
25000
30000
35000
40000
45000
50000
60000
70000
80000
90000
100000
150000
Dopo aver generato casualmente (funzione random()) i valori del vettore di input da
ordinare, per calcolare i tempi di esecuzione di questo algoritmo stato necessario
eseguire il codice pi volte, per una maggior precisione di calcolo dei valori dei
tempi; in particolare:
- per n = 10 - 200 stato scelto di eseguirlo 10000 volte;
- per n = 200 - 1400 stato scelto di eseguirlo 1000 volte;
- per n = 1500 - 9000 stato scelto di eseguirlo 100 volte;
- per n = 10000 in poi stato scelto di eseguirlo 10 volte;
Si tiene presente che la dimensione del bucket pari a 10 (10 posizioni -> 10 liste)
per input fino a 3000 (3000 elementi da ordinare), mentre pari a 100 per input
superiori, fino ad un numero di elementi tale da trovarsi ad un passo prima del caso
peggiore.
Grafico:
0,03
0,025
0,02
0,015
BucketSort
c1
c2
0,01
0,005
Tabella:
n
10
50
100
105
110
115
120
125
150
200
500
510
520
530
540
550
560
570
580
590
600
1000
1010
1020
1030
1040
1050
2000
5000
10000
25000
50000
100000
bucketsort
c2
0,0000078
0,0000453
0,000551
0,000531
0,000594
0,000781
0,000703
0,000774
0,000328
0,000547
0,01125
0,01116
0,01101
0,01173
0,01616
0,01431
0,01415
0,01596
0,0187
0,01548
0,01529
0,04111
0,04094
0,04313
0,04253
0,04377
0,0424
0,0481
0,31
1,22
7,5
29,935
122,42
c1
0,0000002
0,000005
0,00002
0,00002205
0,0000242
0,00002645
0,0000288
0,00003125
0,000045
0,00008
0,0005
0,0005202
0,0005408
0,0005618
0,0005832
0,000605
0,0006272
0,0006498
0,0006728
0,0006962
0,00072
0,002
0,0020402
0,0020808
0,0021218
0,0021632
0,002205
0,008
0,05
0,2
1,25
5
20
n^2
0,000002
0,00005
0,0002
0,0002205
0,000242
0,0002645
0,000288
0,0003125
0,00045
0,0008
0,005
0,005202
0,005408
0,005618
0,005832
0,00605
0,006272
0,006498
0,006728
0,006962
0,0072
0,02
0,020402
0,020808
0,021218
0,021632
0,02205
0,08
0,5
2
12,5
50
200
100
2500
10000
11025
12100
13225
14400
15625
22500
40000
250000
260100
270400
280900
291600
302500
313600
324900
336400
348100
360000
1000000
1020100
1040400
1060900
1081600
1102500
4000000
25000000
100000000
625000000
2500000000
10000000000
Grafico:
250
200
150
BucketSort
c1
100
c2
50
Infatti, nel caso peggiore lalgoritmo Insertion Sort contribuisce con un fattore pari a
O( ) per ordinare gli elementi di ciascuna lista.
In particolare, questo tipo di andamento maggiormente evidente per valori di input
molto grandi, come riportato dal grafico.
Infine, sulla base dei tempi trovati, sono state calcolate le due costanti, c1 e c2, che
definiscono lintervallo temporale in cui si colloca lesecuzione dellalgoritmo (linea
blu), dividendo cos ciascun valore dei tempi asintotici per due costanti che
differiscono per uno o pi ordini di grandezza.
Capitolo 2: Heap-Sort
2.1 Intro
LHeap Sort un altro particolare algoritmo di ordinamento sul posto che
utilizza una struttura dati (heap) per gestire le informazioni e per poter realizzare
anche un efficiente coda di priorit.
L heap (binario) composto dall array di input considerato come un albero
binario completo, in cui ad ogni nodo dellalbero corrisponde un elemento dellarray.
Questo albero viene riempito da sinistra verso destra in modo che tutti i livelli
vengano completamente riempiti, tranne eventualmente lultimo.
Esistono due tipi di heap: max-heap e min-heap. Di seguito mostrata
limplementazione dell Heap Sort sulla base della creazione di un max-heap secondo
cui lelemento pi grande memorizzato nella radice e il sottoalbero di un nodo
(padre) contiene valori non maggiori di quello contenuto nel nodo (padre) stesso.
Dato che le operazioni fondamentali sugli heap vengono eseguite in un tempo
che al massimo proporzionale allaltezza dellalbero (lg(n)), il tempo di esecuzione
complessivo dell Heap Sort pari a O(n*
). In particolare, sia il caso migliore
che il caso peggiore sono limitati inferiormente da una complessit pari a
(n*
).
2.1 Codice
Di seguito riportato il codice in C++ organizzato secondo la
programmazione a moduli che prevede la creazione di un progetto contenente, oltre
al programma principale di esecuzione dellalgoritmo (main.ccp), anche il modulo
relativo alle diverse funzioni necessarie richiamate dall algoritmo stesso
(heapsort.cpp) e una libreria che le potesse includere (lib.h).
<stdio.h>
<iostream>
<vector>
<math.h>
<stdlib.h>
// Propriet max-heap
// Propriet min-heap
#endif /* HEAPSORT_LIB_H_ */
<cstdlib>
<stdio.h>
<iostream>
<time.h>
<algorithm>
"lib.h"
cout <<"\n";
std::cout.setf( std::ios::fixed, std:: ios::floatfield );
std::cout.precision(9);
cout <<(((double)(t))/CLOCKS_PER_SEC)<<"\n";
//system("PAUSE");
return EXIT_SUCCESS;
}
Dall output si pu notare che le due procedure, a partire dallo stesso input, non
generano lo stesso heap, in quanto mentre con la Build_Max_Heap la propriet dell
heap viene rispettata dopo aver creato tutto il vettore di input e quindi il suo
corrispondente albero analizzandolo dal basso verso lalto, la Build_Max_Heap_1,
invece, chiamando la procedura Heap_Increase_Key, rispetta la propriet ad ogni
successivo inserimento.
Tabella:
n
393216
786432
1572864
3145728
6291456
12582912
heapsort c2
c1
n*log(n)
0,344
0,146158092
0,487193641
7307904,615
0,734
0,308044825
1,026816082
15402241,23
1,516
0,647546929
2,158489764
32377346,46
3,154
1,358008418
4,526694728
67900420,92
6,625
2,841845957
9,472819856
142092297,8
14,068
5,935350153
19,78450051
296767507,7
Grafico:
25
20
15
HeapSort
c1
10
c2
0
393216
786432
1572864
3145728
6291456
12582912
Infine, sulla base dei tempi trovati, sono state calcolate le due costanti, c1 e c2, che
definiscono lintervallo temporale in cui si colloca lesecuzione dellalgoritmo (linea
blu), dividendo cos ciascun valore dei tempi asintotici per due costanti che
differiscono per uno o pi ordini di grandezza.
build_max_heap_v1
build_max_heap_v2
(n)
0,00001
0,00008
10
(n*log(n))
33,21928095
Qui, invece, sono riportati i tempi di esecuzione impiegati dalle due versioni di Heap
Sort precedentemente discusse. Dai dati ottenuti per le Build, ci si pu aspettare che,
a parit di input, la v2 impiega un tempo maggiore della v1, per la presenza del
logaritmo. Al lato riportato anche un valore di limite superiore corrispondente alla
complessit (n*
):
n
heap_v1
1000000
heap_v2
1,213
(n*log(n))
1,444
19931568,57
extract
c2
0,00000094
9,53E-07
1,1298E-06
1,164E-06
1,2294E-06
c1
6,64386E-07
7,97263E-07
9,3014E-07
9,7014E-07
1,02302E-06
8,30482E-07
9,96578E-07
1,16267E-06
1,21267E-06
1,27877E-06
log(n)
16,60964047
19,93156857
23,25349666
24,25349666
25,57542476
Grafico:
0,0000014
0,0000012
0,000001
0,0000008
HeapExtractMax
c1
0,0000006
c2
0,0000004
0,0000002
0
100000
1000000
Capitolo 3: Strongly-Connected-Components
3.1 Intro
La Strongly-Connected-Components (SCC) rappresenta una particolare
applicazione del meccanismo di visita in profondit in un grafo (orientato) in cui
possibile trovare le componenti fortemente connesse (SCC), dove per SCC si intende
un insieme di vertici del grafo tale che, per ogni coppia di vertici dellinsieme, questi
sono raggiungibili luno dall altro.
Il grafo rappresentato tramite liste di adiacenza, dove ogni lista, una per
ciascun vertice (o nodo) del grafo, contiene tutti i vertici adiacenti al vertice corrente.
Visitare in profondit quindi tale grafo significa ispezionare tutti gli archi
dellultimo vertice v scoperto che ha ancora archi da visitare, tornando infine indietro
per ispezionare gli archi che escono dal vertice dal quale v stato visitato. Inoltre,
tramite un meccanismo di colorazione, un vertice BIANCO se non stato ancora
scoperto, GRIGIO se viene scoperto durante la visita ma i suoi adiacenti non sono
stati tutti ispezionati, NERO se stato completato, cio se la sua lista di adiacenza
stata completamente ispezionata.
Lalgoritmo, con tempo lineare pari a (V + E), calcola quindi le componenti
fortemente connesse di un grafo orientato, utilizzando due visite in profondit, cio
una sul grafo G principale e unaltra sulla versione trasposta dello stesso.
3.2 Codice
Di seguito riportato il codice in C++ organizzato secondo la
programmazione a moduli che prevede la creazione di un progetto contenente, oltre
al programma principale di esecuzione dellalgoritmo (main.ccp), anche il modulo
relativo alle diverse funzioni necessarie richiamate dall algoritmo stesso (scc.cpp) e
una libreria che le potesse includere (lib.h).
Questo il costruttore del grafo che crea V nodi ed inizializza le relative liste di
adiacenza.
void Grafo::DFS (int predecessore [], int inizio [], int fine [], int colore [],
bool trasposto, list<int> visitati []){
static int time = 0;
if (!trasposto)
{
for (int i = 0; i < V; i++)
{
colore[i] = WHITE;
inizio[i] = NIL;
fine[i] = NIL;
predecessore[i] = NIL;
}
for (int i = 0; i < V; i++)
{
if (colore[i] == WHITE)
{
DFS_Visit(i, inizio, time, fine, colore, predecessore, visitati);
}
}
}
else {
int *fine_trasposto = new int[V];
for (int i = 0; i < V; i++)
{
colore[i] = WHITE;
inizio[i] = NIL;
fine_trasposto[i] = NIL;
predecessore[i] = NIL;
}
for (int j=0; j<V; j++)
{
int max, vertice_max = 0;
for (int i=0; i<V; i++)
{
if (max<fine[i])
{
max = fine[i];
vertice_max = i;
}
}
fine[vertice_max]=0;
if (colore[vertice_max]==WHITE
DFS_Visit(vertice_max, inizio, time, fine_trasposto, colore,
predecessore, visitati);
}
}
}
Questa procedura viene chiamata sia per il grafo iniziale che per la sua versione
trasposta. Grazie ad una variabile booleana, viene effettuato un controllo per poter
agire sul grafo normale oppure sul trasposto.
Dopo la fase di inizializzazione, in cui inizializzata anche una lista dei predecessori
dei vertici volta per volta visitati, la visita procede tramite il meccanismo di
colorazione dei nodi, a partire dal colore BIANCO. Il ramo else viene eseguito solo
nel caso in cui si stia operando sul grafo trasposto. Inoltre, necessario memorizzare
i tempi di fine visita per ciascun vertice del grafo di partenza perch essenziali per
dare inizio alla visita sul grafo trasposto. A tale scopo viene inizializzato il seguente
vettore :
fine_trasposto[i] = NIL
e ad ogni iterazione vado a trovare il tempo di fine visita pi grande calcolato
precedentemente dall'esplorazione del grafo normale. Infine necessario azzerare tale
tempo perch cos alla prossima iterazione si pu prendere il tempo di fine successivo
pi grande.
void Grafo::DFS_Visit (int u, int inizio [], int time, int fine [], int colore
[], int predecessore [], list<int> visitati [])
{
time = time+1;
inizio[u] = time;
colore[u] = GRAY;
list<int>::iterator i;
for (i = adiacenza[u].begin(); i != adiacenza[u].end(); i++)
{
int v = *i;
if (colore[v] == WHITE)
{
predecessore[v] = u;
visitati[u].push_back(v);
DFS_Visit(v, inizio, time, fine, colore, predecessore, visitati);
}
else if (colore[v] == GRAY && v!=predecessore[u] &&
predecessore[u]!=NIL)
{
visitati[u].push_back(v);
visitati[predecessore[u]].insert(visitati[predecessore[u]].end(),
visitati[u].begin(), visitati[u].end ());
}
}
Grafo Grafo::Trasposto()
{
Grafo g(V);
for (int v = 0; v < V; v++)
{
list<int>::iterator i;
for(i = adiacenza[v].begin(); i != adiacenza[v].end(); i++)
{
g.adiacenza[*i].push_back(v);
}
}
return g;
}
Questa procedura genera il grafo trasposto del grafo attuale, salvando in maniera
inversa tutte le varie adiacenze.
void Grafo::SCC()
{
int *colore = new int[V];
int *inizio = new int[V];
int *fine = new int[V];
int *predecessore = new int[V];
int *predecessore_trasposto = new int[V];
list<int> *visitati = new list<int>[V];
list<int> *visitati_trasposto = new list<int>[V];
DFS(predecessore, inizio, fine, colore, false, visitati);
Grafo trasposto = Trasposto();
trasposto.DFS (predecessore_trasposto, inizio, fine, colore, true,
visitati_trasposto);
bool stampato [V];
for (int i=0; i<V; i++)
{
stampato[i] = false;
<cstdlib>
<iostream>
<time.h>
"lib.h"
V+E
100
150
200
250
300
350
1000
1500
2000
2500
10000
11000
15000
20000
21000
22000
Grafici:
0,007
0,006
0,005
0
50
0,004
100
150
0,003
200
0,002
200
150
100
0,001
50
0
0
100
0,007
0,006
0,005
0
0,004
500
0,003
1000
0,002
1000
0,001
500
0
0
1000
Dal grafico si evince un andamento temporale lineare con il numero (fissato) di vertici e con
quello degli archi (variabile: E<=V), nel rispetto del risultato ottenuto dalla sua analisi asintotica.
Infatti, la complessit dellalgoritmo dellordine di (V + E), dove V il numero dei vertici, E
numero degli archi.
0,43
0,425
0,42
0,415
1000
0,41
5000
0,405
10000
5000
1000
0,4
0,395
10000
0,39
10000
Dal grafico si evince un andamento temporale lineare con il numero (fissato) di vertici e con
quello degli archi (variabile: E<=V), nel rispetto del risultato ottenuto dalla sua analisi asintotica.
Infatti, la complessit dellalgoritmo dellordine di (V + E), dove V il numero dei vertici, E
numero degli archi.
Grafici:
0,4
0,35
0,3
100
0,25
1000
2000
0,2
5000
0,15
10000
10000
5000
2000
1000
100
0,1
0,05
0
100
35
30
25
50000
20
70000
15
90000
90000
10
70000
50000
0
1000
Dal grafico si evince un andamento temporale lineare con il numero (fissato) di vertici e con
quello degli archi (variabile) per E<=V, nel rispetto del risultato ottenuto dalla sua analisi
asintotica.
Infatti, la complessit dellalgoritmo dellordine di (V + E), dove V il numero dei vertici, E
numero degli archi.
180
160
140
120
100000
100
150000
80
200000
60
200000
40
150000
20
0
100000
10000
Dal grafico si evince un andamento temporale lineare con il numero (fissato) di vertici e con
quello degli archi (variabile) per E<=V, nel rispetto del risultato ottenuto dalla sua analisi
asintotica.
Infatti, la complessit dellalgoritmo dellordine di (V + E), dove V il numero dei vertici, E
numero degli archi.
SCC
0 0,00013
0
0,005
0
0,39
Grafico:
SCC
0,45
0,4
0,35
0,3
0,25
SCC
0,2
0,15
0,1
0,05
0
100
1000
10000
Per V crescente, senza costruire alcun arco tra i vertici, si nota una sostanziale
crescita dei tempi di esecuzione. In particolare, per V>>N, cio per un numero di
vertici molto grande, si nota un degrado dei tempi di rendimento dellalgoritmo.
In conclusione, dagli esempi mostrati possibile notare un andamento lineare (@(V+E)) dei tempi
di esecuzione, nel rispetto dellanalisi asintotica dellintero algoritmo. Infatti:
Passo 1: esecuzione DFS su grafo principale di input -> @(V+E);
Passo 2: esecuzione trasposizione del grafo principale -> O(V+E) (analisi di tutte le liste di
adiacenza dei V vertici che compongono il grafo);
Passo 3: esecuzione DFS su grafo trasposto -> @(V+E);
Passo 4: output finale delle componenti fortemente connesse ricercate -> lineare (operazione di
intersezione).