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.