STRUTTURA DATI: GRAFO NON ORIENTATO
Un grafo non orientato è una collezione di nodi (vertici) connessi da archi che rappresentano relazioni bidirezionali.
PROPRIETÀ PRINCIPALI
- Rappresentato come coppia (V, E):
- V → insieme dei vertici (nodi).
- E → insieme degli archi (coppie non ordinate di vertici).
- Gli archi non hanno direzione:
se esiste un arco traAeB, si può navigare sia daAaBche daBaA.
TERMINOLOGIA
- Vertice (Vertex) → un nodo del grafo.
- Arco (Edge) → connessione tra due vertici.
- Grado (Degree) → numero di archi connessi a un vertice.
- Percorso (Path) → sequenza di vertici connessi da archi.
- Ciclo (Cycle) → percorso chiuso (inizio = fine).
- Componente Connessa → sottoinsieme di vertici in cui ogni coppia è collegata direttamente o indirettamente.
OPERAZIONI PRINCIPALI
aggiungiVertice(v)→ inserisce un nuovo vertice.aggiungiArco(v1, v2)→ crea un arco tra due vertici (aggiunge i vertici se non esistono).rimuoviArco(v1, v2)→ elimina un arco se presente.rimuoviVertice(v)→ elimina un vertice e tutti i suoi archi.verificaConnesso(v1, v2)→ controlla se esiste un percorso tra due vertici.visitaAmpiezza(v)→ BFS a partire da un vertice.visitaProfondità(v)→ DFS a partire da un vertice.
RAPPRESENTAZIONE
- Lista di adiacenza (efficiente in grafi sparsi):
Esempio → { A: [B, C], B: [A, D], C: [A], D: [B] } - Matrice di adiacenza (più adatta in grafi densi).
EFFICIENZA
- Inserimento vertice → O(1)
- Inserimento arco → O(1)
- Rimozione arco → O(d) (d = grado massimo tra i due vertici).
- Attraversamento BFS/DFS → O(V + E)
APPLICAZIONI
- Reti sociali.
- Mappe stradali.
- Reti di computer.
- Pianificazione di percorsi senza direzione.
- Analisi di sistemi complessi.
VANTAGGI
- Struttura flessibile, adatta a molti problemi reali.
- Operazioni ottimizzabili a seconda della rappresentazione.
LIMITAZIONI
- Grafi densi → memoria elevata.
- Operazioni come rimozione arco o verifica connessione possono essere costose rispetto a strutture specializzate.
NOTE
- Base per algoritmi fondamentali:
- calcolo delle componenti connesse,
- rilevamento cicli,
- cammino minimo.
CODICE
/**
* Interfaccia che rappresenta un arco pesato.
*/
export interface Edge<E> {
node: E;
weight: number;
}
/**
* Implementazione di un grafo generico pesato e non orientato.
* @typeParam E - Tipo dei vertici
*/
export class NotOrientedGraph<E> {
private adjacencyList: Record<string, Edge<E>[]>;
constructor() {
this.adjacencyList = {};
}
/**
* Aggiunge un vertice al grafo.
* Se il vertice è già presente, non fa nulla.
* @param vertex - Il vertice da aggiungere
*/
aggiungiVertice(vertex: E): void {
const key = String(vertex);
if (!this.adjacencyList[key]) {
this.adjacencyList[key] = [];
}
}
/**
* Aggiunge un arco pesato non orientato tra due vertici.
* Se i vertici non esistono, vengono creati automaticamente.
* @param vertex1 - Primo vertice
* @param vertex2 - Secondo vertice
* @param weight - Peso dell'arco
*/
aggiungiArco(vertex1: E, vertex2: E, weight: number): void {
const v1 = String(vertex1);
const v2 = String(vertex2);
if (!this.adjacencyList[v1]) {
this.aggiungiVertice(vertex1);
}
if (!this.adjacencyList[v2]) {
this.aggiungiVertice(vertex2);
}
this.adjacencyList[v1].push({
node: vertex2, weight
});
this.adjacencyList[v2].push({
node: vertex1, weight
});
}
/**
* Rimuove un arco tra due vertici, se esiste.
* @param vertex1 - Primo vertice
* @param vertex2 - Secondo vertice
*/
rimuoviArco(vertex1: E, vertex2: E): void {
const v1 = String(vertex1);
const v2 = String(vertex2);
this.adjacencyList[v1] = this.adjacencyList[v1]?.filter(edge => edge.node !== vertex2) || [];
this.adjacencyList[v2] = this.adjacencyList[v2]?.filter(edge => edge.node !== vertex1) || [];
}
/**
* Rimuove un vertice e tutti i relativi archi.
* @param vertex - Il vertice da rimuovere
*/
rimuoviVertice(vertex: E): void {
const v = String(vertex);
while (this.adjacencyList[v]?.length) {
const adjacentEdge = this.adjacencyList[v].pop() as Edge<E>;
this.rimuoviArco(vertex, adjacentEdge.node);
}
delete this.adjacencyList[v];
}
/**
* Visualizza il grafo in console.
*/
display(): void {
for (const vertex in this.adjacencyList) {
const edges = this.adjacencyList[vertex];
const rappresentazione = edges.length > 0
? edges.map(e => `${e.node}(${e.weight})`).join(", ")
: "NULL";
console.log(`${vertex} -> ${rappresentazione}`);
}
}
/**
* Verifica se il grafo è connesso.
* @returns true se il grafo è connesso, false altrimenti
*/
verificaConnesso(): boolean {
const visitati = new Set<string>();
const vertices = Object.keys(this.adjacencyList);
if (vertices.length === 0) return true; // grafo vuoto = connesso
const dfs = (vertex: string) => {
visitati.add(vertex);
for (const edge of this.adjacencyList[vertex]) {
const key = String(edge.node);
if (!visitati.has(key)) {
dfs(key);
}
}
};
dfs(vertices[0]);
return vertices.length === visitati.size;
}
/**
* Visita in ampiezza (BFS) a partire da un vertice.
* @param start - Vertice di partenza
* @returns Lista dei vertici visitati in ordine BFS
*/
visitaAmpiezza(start: E): E[] {
const result: E[] = [];
const visitati = new Set<string>();
const queue: E[] = [start];
visitati.add(String(start));
while (queue.length > 0) {
const nodo = queue.shift() as E;
result.push(nodo);
for (const edge of this.adjacencyList[String(nodo)]) {
const key = String(edge.node);
if (!visitati.has(key)) {
visitati.add(key);
queue.push(edge.node);
}
}
}
return result;
}
/**
* Visita in profondità (DFS) a partire da un vertice.
* @param start - Vertice di partenza
* @returns Lista dei vertici visitati in ordine DFS
*/
visitaProfondita(start: E): E[] {
const result: E[] = [];
const visitati = new Set<string>();
const dfs = (vertex: E) => {
visitati.add(String(vertex));
result.push(vertex);
for (const edge of this.adjacencyList[String(vertex)]) {
if (!visitati.has(String(edge.node))) {
dfs(edge.node);
}
}
};
dfs(start);
return result;
}
/**
* Restituisce tutti i vertici del grafo
* @returns tutti i vertici del grafo
*/
getVertices(): E[] {
const verticesSet = new Set<E>();
for (const key in this.adjacencyList) {
verticesSet.add(key as unknown as E);
for (const edge of this.adjacencyList[key]) {
verticesSet.add(edge.node);
}
}
return Array.from(verticesSet);
}
/**
* Restituisce tutti gli archi senza duplicati
* @returns tutti gli archi senza duplicati
*/
getEdges(): { from: E; to: E; weight: number }[] {
const edges: { from: E; to: E; weight: number }[] = [];
const visited = new Set<string>();
for (const key in this.adjacencyList) {
for (const edge of this.adjacencyList[key]) {
// Crea un id unico indipendente dall'ordine dei nodi
const id = [String(key), String(edge.node)].sort().join('-');
if (!visited.has(id)) {
edges.push({ from: key as unknown as E, to: edge.node, weight: edge.weight });
visited.add(id);
}
}
}
return edges;
}
/**
* Fornisce la lista di adiacenza dei nodi del grafo
* @returns lista di adiacenza
*/
getAdjacencyList(){
return this.adjacencyList;
}
}
// =============================
// Test della struttura dati
// =============================
const graph = new NotOrientedGraph<string>();
graph.aggiungiVertice("A");
graph.aggiungiVertice("B");
graph.aggiungiVertice("C");
graph.aggiungiArco("A", "B", 5);
graph.aggiungiArco("A", "C", 2);
graph.aggiungiArco("B", "C", 3);
graph.display();
console.log("\nGrafo connesso:", graph.verificaConnesso());
console.log("Visita in ampiezza da A:", graph.visitaAmpiezza("A"));
console.log("Visita in profondità da A:", graph.visitaProfondita("A"));
graph.rimuoviArco("A", "C");
console.log("\nDopo aver rimosso l'arco A-C:");
graph.display();
graph.rimuoviVertice("B");
console.log("\nDopo aver rimosso il vertice B:");
graph.display();
console.log("\nGrafo connesso:", graph.verificaConnesso());
console.log("Visita in ampiezza da A:", graph.visitaAmpiezza("A"));
console.log("Visita in profondità da A:", graph.visitaProfondita("A"));