RELAZIONE PROGETTO ALGORITMI
JESSICA CHIARA CATANIA E CRISTINA PARRACELLO PARASILITI
Minimum Spanning Tree
(Kruskal & Prim)
STRUMENTI UTILIZZATI
È stato usato il linguaggio Java per programmare questa applicazione e tutte le librerie
necessarie per la grafica in Java.
STRUTTURA DEL PROGETTO
L’applicazione è separata in dieci classi.
Le classi MyApplet o MaFrame servono per avviare il programma. Queste due classi
estendono rispettivamente la classe Applet e la classe JFrame.
Le classi Node e Arc sono gli oggetti nodo e arco rispettivamente e sono utilizzate
dalle classi Kruskal e Prim .
Le tre classi principali per la progettazione generale e la grafica dell’applicazione sono
le classi Dessin , North e South.
Le classi TxtFileFilter e White servono rispettivamente per il salvataggio sul file e per il
caricamento da file.
La classe Dessin estende la classe JPanel. Questa classe può visualizzare tutto il design
di vertici e archi con Graphics2D.
La classe Graphics2D estende la classe Graphics per fornire sofisticati controlli sulla
geometria, trasformazioni di coordinate, gestione del colore e la disposizione del testo.
Questa è la classe fondamentale per il rendering di forme bidimensionali, testi ed
immagini sulla piattaforma Java.
Le classi North e South devono avere un oggetto Dessin da eseguire, poiché è con
queste classi che l’utente interagisce.
INTERFACCIA UTENTE
L’interfaccia grafica si compone di tre parti principali.
La prima parte permette all’utente di scegliere quale algoritmo deve essere utilizzato.
Un esempio è il seguente :
Figura1
La seconda parte principale è la parte in cui si formano gli alberi.
L’utente sceglie il numero di nodi e l’applicazione non farà altro che creare tante forme
ovali quanti sono i numeri di nodi e per ciascuno di essi associarne un numero.
Figura2
L’ultima parte principale permette all’utente di definire gli archi dell’albero. Si scrivono
i due nodi ed il peso relativo all'arco associato ai due nodi. Cliccando poi sul pulsante
set verrà creato l’arco tra i due nodi e visualizzato a video, come segue :
Figura 3
Vengono raccolte tutte le informazioni e immagazzinate su diverse stringhe. Si effettua
una verifica su ogni stringa, per controllare se l’utente non metta due nodi uguali per
creare l’arco oppure il controllo sul valore del peso dell’arco.
Infine quando l’utente clicca sul pulsante Esegui viene eseguito l’algoritmo prescelto.
Cliccando ogni volta il pulsante si vedrà a video l’esecuzione passo dopo passo
dell’avanzamento del programma.
Figura 4
Alla fine si vedrà visualizzato l’MST calcolato dall’algoritmo prescelto.
L’utente può riavviare l’applicazione scegliendo nuovi nodi o un altro algoritmo.
L’utente può anche caricare e/o salvare il proprio grafo tramite i pulsanti “Carica da
file” ed “Salva Grafo”.
Con il pulsante di caricamento l’utente non deve fare altro che creare un file txt
strutturato nel seguente modo:
1°riga:numero totale di nodi
2°riga: 1°nodo , 2° nodo, peso/costo (esempio 3,5,10  crea un arco che collega il
nodo 3 ed il nodo 5 con peso 10)
… cosi via per tutte le altre righe
Con il pulsante di salvataggio il grafo viene salvato in formato txt ( struttura uguale a
quella spiegata sopra) in modo tale che l’utente possa salvare il grafo creato, per un
possibile riutlizzo istantaneo.
SPIEGAZIONE DEL CODICE
La classe Dessin è la classe principale per disegnare archi e vertici dell’albero.
Viene effettuata una chiamata al costruttore della classe padre per inizializzare il
grafico g per poi creare una grafica2D da g.
int largeur = getSize().width;
int hauteur = getSize().height;
Viene recuperata l’altezza e la larghezza che poi viene utilizzata per posizionare i
differente design.
Tramite un ciclo (da 0 al numero di archi) vengono selezionati i due vertici di ogni arco
e calcolato l’angolo rispetto al cerchio trigonometrico.
int sommet1=arbre[i].getSommet1();
int sommet2=arbre[i].getSommet2();
angle =(sommet1-1)*(2*Math.PI / nbNoeud);
angle1=(sommet2-1)*(2*Math.PI / nbNoeud);
poi vengono calcolate le coordinate per disegnare l’arco selezionato con l’altezza e la
larghezza con coseno e seno.
int x=(int)( (largeur/2)-( (largeur/4) *Math.cos(angle))+15 );
int y =(int) ( hauteur/2- (hauteur/4) *Math.sin(angle)+15 );
int x2=(int)( (largeur/2)-( (largeur/4) *Math.cos(angle1))+15);
int y2=(int) ( hauteur/2- (hauteur/4) *Math.sin(angle1)+15 );
dopo aver calcolato le coordinate è possibile cambiare il colore e tracciare la linea sul
relativo peso.
g2.setColor(Color.gray);
g2.drawLine(x,y,x2,y2);
g.drawString(""+(arbre[i].getCout()) , (int) ((x+x2)/2 +30),
(int) ((y+y2)/2 +30) );
dopo si testa se l’albero finale è stato creato.
Se la matrice non è vuota e se l’utente clicca sul pulsante step, si disegnano gli archi
tanti quanti ha richiesto l’utente.
Prima di disegnare viene salvato il contesto grafico, si pratica uno zoom di due pixel e
si disegnano archi e attribuiti pesi agli archi e viene ripristinato il vecchio contesto
grafico.
Stroke s = g2.getStroke();
g2.setStroke(new BasicStroke(2));
/* all the code to draw arc and his weight */
g2.setStroke(s);
Per finire verrà disegnato ciascun vertice. Per ogni vertice dobbiamo cambiare colore
in grigio scuro e dobbiamo disegnare un intorno ovale al cerchio trigonometrico.
Dobbiamo cambiare il colore in bianco e disegnare il numero di vertici in un ovale.
Infine verrà modificato l’angolo per poter disegnare un altro vertice.
g2.fillOval( (int)( (largeur/2)-( (largeur/4) *Math.cos(angle))),
(int) ( hauteur/2- (hauteur/4) *Math.sin(angle) ), 30, 30) ;
g.setColor(Color.white);
g.drawString(""+(i+1) , (int)( (largeur/2)-( (largeur/4)
*Math.cos(angle)) +10), (int) ( hauteur/2- (hauteur/4)
*Math.sin(angle)+20 ));
angle+=2*Math.PI / nbNoeud;
La classe Kruskal
L’algoritmo Kruskal consiste nel disporre per ordine di peso tutti gli archi del grafo,
disegnarne uno per volta (se non forma nessun ciclo).
Per una maggiore efficienza la colorazione avviene nel seguente modo
- 1 passo: un arco blu ed un arco rosso
- 2 passo: un arco blu ed un arco rosso
… fino a quando non esistono più archi rossi e gli archi vengono colorati solo di blu.
Nel costruttore, dobbiamo fare solo una chiamata al metodo kruskal con i parametri
mancanti.
Inizializziamo l’array papa a -1 per tutti gli array di interi. Questo array sarà utilizzato
per verificare se sono presenti cicli o meno.
Facciamo un ciclo da 0 al numero di archi oppure da 0 al numero di nodi. Testiamo se
non c’è un ciclo e se è vero inseriamo l’arco sull’albero finale.
Ritorniamo l’intero temporaneo nb che sarà uguale al numero di nodi -1.
init(papa);
int ia = 0;
int nb = 0;
triGraph(g,nbarret);
while (ia < nbarret && nb < nbnoeud){
if (cyclep(papa, g[ia].getSommet1(), g[ia].getSommet2())){
arbre[nb] = g[ia];
nb++;
}
ia++;
}
return nb == nbnoeud - 1;
Per testare se esiste un ciclo sull’albero, usiamo un array di interi inizializzato a -1 per
tutti i valori.
Chiamiamo il metodo cyclep passando come input questo array e i due vertici
dell’arco. Controlliamo nell’array se il valore dell’array nella posizione del primo nodo è
maggiore di zero e se lo è, immagazziniamo il nodo e continuiamo con questo nuovo
indice. Quando il valore dell’array non è superiore a zero, passiamo al controllo
dell’array sull’indice del secondo nodo passato. Quando i controlli su entrambi sono
finiti, si controlla se i due nuovi numeri sono uguali.
public boolean cyclep(int[] t, int a, int b) {
int i = a;
int j = b;
while (t[i] > 0){
i = t[i];
}
while (t[j] > 0){
j = t[j];
}
if (i != j){
t[i] = j;
}
return i != j;
}
La classe Prim
Il primo passo consiste nella scelta di un vertice casualmente. In questo modo
abbiamo un insieme contenente un vertice e zero archi. Poi costruiamo l’abero minimo
ricorsivamente nel seguente modo:
nella fase n abbiamo già costruito un albero contente n vertici e n-1 archi, disegniamo
la lista di tutti gli archi vincolati con il vertice dell’insieme e con il vertice fuori
dall’insieme. Poi scegliamo un arco con il peso minimo, e l’aggiungiamo all’albero.
adesso l’albero contiene n+1 vertici e n archi. L’algoritmo termina quando tutti i vertici
del grafo sono contenuti nell’insieme.
Nel costruttore inizializziamo l’array con il nodo inserito dall’utente e facciamo una
chiamata al metodo prim con i parametri mancanti.
public void initNod(int nbnoeud){
node = new Node[nbnoeud];
for (int i=0;i<nbnoeud;i++){
node[i]=new Node(i+1);
}
}
Inizializziamo l’array papa a -1 per tutti gli array di interi e l’array tab a false per tutti
gli array di booleani. L’array papa deve essere usato per il verificare se ci sono cicli o
meno e l’array tab deve essere utilizzata per verificare se ci sono nodi che non devono
essere inseriti nell’albero finale.
public boolean chercheEnsemble(int k){
boolean tmp=true;
for(int i=0;i<k;i++){
if(!tab[i]){
tmp=false;
}
}
return tmp;
}
Inizializziamo il numero casuale con la data di sistema in millesecondi e lo inseriamo
nell’insieme.
triGraph(g,nbarret);
r.setSeed(date.getTimeInMillis());
Node tmp = node[r.nextInt(nbnoeud)];
tab[tmp.GetSommet() -1]=true;
Facciamo poi un ciclo finchè tutti i nodi non sono nell’insieme facciamo un secondo
ciclo che va da 1 al numero di nodi. Salviamo in un array gli archi temporanei, tutti gli
archi che collegano un nodo nell’insieme con un nodo che non fa parte di esso, e
aggiorniamo la variabile con il numero di archi che ho trovato.
int i=1;
int cpt=0;
while(i<=nbnoeud){
for(int j=0;j<nbarret;j++){
if(i==g[j].getSommet1() || i==g[j].getSommet2()){
if( (tab[ ( g[j].getSommet1() -1 ) ] && !tab[ (
g[j].getSommet2() -1 ) ])||(!tab[ (
g[j].getSommet1() -1 ) ] && tab[ ( g[j].getSommet2()
-1 ) ]) ){
temporaire[cpt]=g[j];
cpt++;
}
}
}
i++;
}
Verifichiamo poi se non c’è un ciclo , e se è cosi inseriamo l’arco nell’albero finale e
mettiamo il nodo nell’insieme. Quando il ciclo finisce, torniamo l’intero temporaneo nb
che è uguale al numero di nodi -1.
if(cpt!=0){
triGraph(temporaire,cpt);
while(ia<cpt && !find){
if(cycle(papa,temporaire[ia].getSommet1(),
temporaire[ia].getSommet2())){
arbre[nb] = temporaire[ia];
nb++;
tab[temporaire[ia].getSommet1() -1]=true;
tab[temporaire[ia].getSommet2() -1]=true;
find=true;
}
ia++;
}
}
ESEMPIO ALGORITMO DI KRUSKAL NELLA DEMO REALIZZATA
Questo è il grafo originale:
Figura 5
L’arco [1-2] è quello con peso minore.
Figura 6
L’arco [2-5] è ora l’arco con peso minore e non forma un ciclo.
Figura 7
L’arco [2-3] è adesso l’arco con il peso minore e non forma alcun ciclo.
Figura 8
Il prossimo arco con peso minore è [1-5] ma esso formerebbero un ciclo se venisse
scelto.
L’arco [4-5] è allora il prossimo arco con peso minore che non forma un ciclo da
selezionare.
Figura 9
Il processo termina qui poiché rimane come arco con peso minimo da selezionare il [34] ma esso formerebbe il ciclo se venisse selezionato.
L’immagine rappresenta l’albero di copertura minimo relativo al grafo iniziale.
ESEMPIO ALGORITMO DI PRIM NELLA DEMO REALIZZATA
Questo è il grafo originale:
Figura 10
Il vertice 5 viene scelto arbitrariamente come punto di inizio.
Inseriamo i 5 vertici nell’insieme. I vertici 1,2,3,4 sono connessi con il vertice 5 da un
singolo arco. il vertice 1 è il vertice più vicino al vertice 5 e verrà scelto come secondo
vertice dell’insieme con l’arco [1-5].
Figura 11
Il prossimo vertice scelto è il vertice piu vicino al vertice al 5 o al vertice 1. Il vertice 2
dista 10 dal vertice 1, il vertice 3 dista 90 da 5 e il vertice 4 dista 60 dal vertice 5. Il
vertice 2 è quello con distanza minore e non forma un ciclo , viene quindi scelto
dall’insieme dei vertici.
Figura 12
In questo caso dobbiamo scegliere tra il vertice 3 ed il 4. Il vertice con distanza
minima è il vertice 4 con l’arco [4-5] che non forma un ciclo.
Figura 13
Il vertice 3 è l’unico rimasto nell’insieme. Selezioniamo l’arco [3-5] con distanza
minima ed esso non forma un arco.
Figura 14
Non ci sono più vertici da selezionare. Il processo termina e questo albero rappresenta
l’albero di copertura minimo relativo al grafo iniziale.