Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
return del == 2;
Ho usato una sorta di flag (del) per essere sicuro che il numero totale degli archi
rimossi siano 2, in quanto riferito a ciò che prima ho spiegato sulla loro aggiunta.
Per quanto riguarda i test invece, ho usato come riferimento il seguente grafo:
/*
* Essendo un'operazione non supportata non ne eseguo il test
*/
@Disabled
final void testGetNodeAtIndex() {
fail("Not yet implemented");
}
Un grafo è detto orientato se ogni singolo arco è individuato da una coppia ordinata
di nodi. Dato un grafo orientato ed un arco w(u, v) appartenente ad esso si può dire
che:
• u è l’origine e v la destinazione dell’arco w;
• u e v sono nodi adiacenti;
• u è il predecessore di v e v è il successore di u;
• w è uscente da u ed entrante in v.
// Insieme dei nodi e associazione di ogni nodo con il proprio indice nella
// matrice di adiacenza
protected Map<GraphNode<L>, Integer> nodesIndex;
@Override
public boolean addNode(GraphNode<L> node) {
/*
* Per aggiungere un nodo al grafo, mi devo innanzitutto accertare che
* questo non sia già presente all'interno dell'insieme indexNodes.
* Se questo non è presente, procedo con l'inserimento del nodo
* nell'insieme indexNodes. Una volta nell'insieme devo aggiornare
* la grandezza della matrice, incrementando le righe e le colonne.
* I valori dei nuovi campi sono impostati a null
*/
Una coda di priorità è una struttura dati che serve a mantenere in ordine un insieme
S di elementi, ciascuno con un valore associato detto chiave. Una coda di min-
priorità supporta le seguenti operazioni:
return heap.get(0);
}
@Override
public void computeShortestPathsFrom(GraphNode<L> sourceNode) {
// In caso di parametro null
if(sourceNode == null){
throw new NullPointerException("Parametro null!");
}
// Non non appartenente al grafo
if(!graph.containsNode(sourceNode)){
throw new IllegalArgumentException("Il nodo non esiste nel grafo");
}
// IllegalStateException non necessario
// Inizializzo i nodi
initSingleSource(sourceNode);
// Creazione queue dopo che ho il sourceNode
createQueue();
while(!queue.isEmpty()){
GraphNode<L> node = (GraphNode<L>) queue.extractMinimum();
computedNodes.add(node);
for(GraphNode<L> adjNode : graph.getAdjacentNodesOf(node)){
relax(graph.getEdge(node, adjNode));
}
}
this.computed = true;
this.lastSource = sourceNode;
}
Le classi aggiuntive utili all’implementazione sono le seguenti:
• initSingleSource(GraphNode<L> sourceNode): inizializza le stime dei cammini
minimi e i predecessori;
private void initSingleSource(GraphNode<L> s) {
if (s == null) {
throw new NullPointerException("Parametro null!");
}
for (GraphNode<L> node : graph.getNodes()) {
if (!node.equals(s)) {
node.setFloatingPointDistance(Double.MAX_VALUE);
node.setPrevious(null);
} else {
node.setFloatingPointDistance(0);
}
}
}
• createQueue(): metodo che crea la coda di priorità per l’algoritmo;
private void createQueue(){
for(GraphNode<L> node : graph.getNodes()){
queue.insert(node);
}
}
• relax(GraphEdge<L> toRelax): metodo di rilassamento di un’arco
private void relax(GraphEdge<L> toRelax) {
if (toRelax == null) {
throw new NullPointerException("Parametro null!");
}
if (toRelax.getNode2().getFloatingPointDistance() >
toRelax.getNode1().getFloatingPointDistance() + toRelax.getWeight()) {
toRelax.getNode2().setFloatingPointDistance(toRelax.getNode1().getFloatingPointDis
tance() + toRelax.getWeight());
toRelax.getNode2().setPrevious(toRelax.getNode1());
}
}
Per quanto riguarda il metodo che riesce a tirarmi fuori il percorso di archi
attraversato dall’algoritmo per raggiungere un certo nodo, il meccanismo è
particolare. Infatti sono dovuto partire dal nodo di arrivo ed andare a ritroso fino al
nodo di partenza attraverso il metodo getPrevious() dei nodi, aggiungere ogni arco
attraversato in una List ed una volta arrivato al nodo di partenza fare il reverse() della
lista in modo da avere il percorso in ordine.
Se un tale ciclo esiste all’interno del grafo, il problema non ha soluzione (si farebbe
l’operazione -4-6 fino all’infinito), altrimenti l’algoritmo restituisce i cammini minimi
ed i loro pesi.
A sinistra la matrice delle distanze tra tutte le coppie di nodi, a destra la matrice dei predecessori, che serve per
estraree il percorso da un nodo sorgente ad un nodo destinazione. Questo, è anche il grafo che ho utilizzato per
la realizzazione dei test.
computed = true;
}
Cosa più complicata invece, è stata la realizzazione del metodo che restituisce il
percorso di archi da un nodo partenza ad un nodo destinazione:
if(sourceNode==targetNode){
return null;
}
// Indice nodo partenza
int i = graph.getNodeIndexOf(sourceNode.getLabel());
// Indice nodo destinazione
int j = graph.getNodeIndexOf(targetNode.getLabel());
// Indice del nodo precedente
int target = predecessorMatrix[i][j];
// Insieme degli archi che formano il percorso
List<GraphEdge<L>> toReturn = new ArrayList<>();
// Il previous del targetNode
GraphNode<L> toCheck = graph.getNodeAtIndex(target);
// Finché non arrivo al nodo di partenza
while(!toCheck.equals(sourceNode)){
// Aggiungo l'arco all'insieme degli archi
toReturn.add(graph.getEdge(toCheck, targetNode));
// Aggiorno l'indice del nodo precedente
target = predecessorMatrix[i][graph.getNodeIndexOf(toCheck.getLabel())];
// Scorro il nodo, andando indietro
targetNode = toCheck;
// Aggiorno con il nuovo previous
toCheck = graph.getNodeAtIndex(target);
}
// Aggiungo l'ultimo arco che collega il sourceNode al nodo successivo
toReturn.add(graph.getEdge(sourceNode, targetNode));
// Faccio il reverse, in modo da avere l'array di archi in ordine
Collections.reverse(toReturn);
return toReturn;
Algoritmo di Kruskal
Nell’algoritmo di Kruskal, l’insieme A è una foresta i cui vertici sono tutti quelli del
grafo. L’arco sicuro aggiunto ad A è sempre un arco di peso minimo nel grafo che
collega due componenti distinte. L’algoritmo trova un arco sicuro da aggiungere alla
foresta in costruzione scegliendo, fra tutti gli archi che collegano due alberi qualsiasi
nella foresta, un arco (u,v) di peso minimo. Kruskal usa una struttura dati per insiemi
disgiunti per mantenere vari insiemi disgiunti di elementi. Ogni insieme contiene i
vertici di un albero della foresta corrente.
disjointSets.get(findSet(node1)+1).add(node2);
disjointSets.remove(no2);
}
Algoritmo di Prim
L’algoritmo di Prim opera in modo molto simile all’algoritmo di Dijkstra per trovare i
cammini minimi di un grafo. Prim ha la proprietà che gli archi nell’insieme A formano
sempre un albero singolo. L’albero inizia da un arbitrario vertice radice r e si sviluppa
fino a coprire tutti i vertici in V. Ad ogni passo viene aggiunto all’albero A un arco
leggero che collega A con un vertice isolato – un vertice che non sia estremo di
qualche arco in A. Questa regola aggiunge soltanto archi che sono sicuri per A,
quando l’algoritmo termina, gli archi in A formano un albero di connessione minimo.
Questa strategia è golosa perché l’albero cresce includendo a ogni passo un arco
che contribuisce con la quantità più piccola possibile a formare il peso dell’albero.
Implementazione proposta:
Ci sono due metodi extra implementati per lavorare con la coda di priorità, non erano
necessari ma li ho aggiunti per cercare di tenere un’implementazione quanto più
modulare possibile.