martedì 19 dicembre 2023

Corso ALGORITMI PER LA PROGRAMMAZIONE: Lezione 10 Algoritmi di Grafo

10- Algoritmi di Grafo: Gli algoritmi di grafo sono utilizzati per risolvere problemi che coinvolgono reti complesse o strutture a grafo. Alcuni esempi sono l'algoritmo di Dijkstra e l'algoritmo di Prim.


Gli algoritmi di grafo sono fondamentali nell'ambito dell'informatica per risolvere una vasta gamma di problemi che coinvolgono reti complesse o rappresentazioni a grafo di dati. Questi algoritmi sono progettati per operare su strutture a grafo e sono utilizzati in molte applicazioni, come reti di computer, reti sociali, mappe, problemi di ottimizzazione e molto altro.


Ecco un breve riassunto di due algoritmi di grafo comuni:


10.1- Algoritmo di Dijkstra: Utilizzato per trovare il percorso più breve da un nodo di partenza a tutti gli altri nodi in un grafo pesato non orientato o orientato, in cui i pesi degli archi rappresentano le distanze tra i nodi. L'algoritmo di Dijkstra utilizza una strategia avida per selezionare di volta in volta il nodo con la distanza minima non ancora inclusa nel percorso più breve. A ogni passaggio, l'algoritmo aggiorna le distanze dei nodi adiacenti attraverso gli archi, se la nuova distanza è minore della distanza corrente. L'algoritmo termina quando tutte le distanze più brevi dai nodi di partenza a tutti gli altri nodi sono state calcolate.


Ecco un esempio di implementazione dell'algoritmo di Dijkstra in Python utilizzando un grafo rappresentato come dizionario di dizionari:

python

Copy code

from heapq import heappop, heappush


def dijkstra(graph, start):

    # Inizializzazione della coda con la distanza dal nodo di partenza

    queue = [(0, start)]

    # Dizionario per tenere traccia delle distanze minime

    distances = {vertex: float('infinity') for vertex in graph}

    distances[start] = 0


    while queue:

        # Estrae il nodo con la minima distanza dalla coda

        current_distance, current_vertex = heappop(queue)


        # Se la distanza attuale è maggiore della distanza calcolata, passa al prossimo nodo

        if current_distance > distances[current_vertex]:

            continue


        # Scorre i vicini del nodo corrente

        for neighbor, weight in graph[current_vertex].items():

            distance = current_distance + weight

            # Se la distanza calcolata è minore della distanza attuale, aggiorna la distanza

            if distance < distances[neighbor]:

                distances[neighbor] = distance

                heappush(queue, (distance, neighbor))


    return distances


# Esempio di utilizzo:


# Grafo rappresentato come dizionario di dizionari (nodo: {nodo vicino: peso})

graph = {

    'A': {'B': 4, 'C': 2},

    'B': {'A': 4, 'D': 5},

    'C': {'A': 2, 'D': 7},

    'D': {'B': 5, 'C': 7}

}


start_node = 'A'

result = dijkstra(graph, start_node)


print("Distanze minime dal nodo di partenza:")

for node, distance in result.items():

    print(f"Nodo: {node}, Distanza: {distance}")

Questo codice implementa l'algoritmo di Dijkstra utilizzando una coda di priorità (heap) per gestire i nodi in ordine crescente di distanza. L'algoritmo calcola le distanze minime da un nodo di partenza a tutti gli altri nodi nel grafo.


Nell'esempio sopra, il grafo è rappresentato come un dizionario di dizionari, dove le chiavi esterne rappresentano i nodi e i dizionari interni rappresentano i nodi vicini e i pesi degli archi.


L'algoritmo restituisce un dizionario con le distanze minime da 'start_node' a tutti gli altri nodi nel grafo.


10.2- Algoritmo di Prim: Utilizzato per trovare un albero di copertura minimo in un grafo pesato connesso non orientato. L'algoritmo di Prim parte da un nodo arbitrario e aggiunge ripetutamente l'arco di peso minimo che connette un nodo già incluso nell'albero a un nodo esterno all'albero. Questo processo continua finché tutti i nodi non sono inclusi nell'albero di copertura minimo.


Ecco un esempio di implementazione dell'algoritmo di Prim in Python per trovare un albero di copertura minimo in un grafo non orientato e con pesi sugli archi rappresentati come un dizionario di dizionari:


python

Copy code

from collections import defaultdict


def prim(graph):

    minimum_spanning_tree = defaultdict(set)

    vertices = set(graph)

    start_vertex = vertices.pop()

    visited = {start_vertex}


    edges = [

        (cost, start_vertex, next_vertex)

        for next_vertex, cost in graph[start_vertex].items()

    ]

    edges.sort()


    while vertices and edges:

        cost, current_vertex, next_vertex = edges.pop(0)


        if next_vertex not in visited:

            visited.add(next_vertex)

            minimum_spanning_tree[current_vertex].add(next_vertex)

            minimum_spanning_tree[next_vertex].add(current_vertex)


            for next_next_vertex, cost in graph[next_vertex].items():

                if next_next_vertex not in visited:

                    edges.append((cost, next_vertex, next_next_vertex))


            edges.sort()


    return dict(minimum_spanning_tree)


# Esempio di utilizzo:


# Grafo non orientato con pesi sugli archi (nodo: {nodo vicino: peso})

graph = {

    'A': {'B': 2, 'C': 3},

    'B': {'A': 2, 'C': 1, 'D': 1},

    'C': {'A': 3, 'B': 1, 'D': 2},

    'D': {'B': 1, 'C': 2}

}


minimum_spanning_tree = prim(graph)

print("Albero di copertura minimo:")

for vertex, neighbors in minimum_spanning_tree.items():

    for neighbor in neighbors:

        print(f"{vertex} - {neighbor}")

In questo esempio, l'algoritmo di Prim è utilizzato per trovare un albero di copertura minimo nel grafo. Il grafo è rappresentato come un dizionario di dizionari dove le chiavi esterne rappresentano i nodi e i dizionari interni rappresentano i nodi vicini e i pesi degli archi.


L'algoritmo di Prim inizia da un nodo arbitrario, esplora i nodi in modo greedy selezionando ripetutamente l'arco con il peso minimo che collega un nodo già incluso nell'albero di copertura a un nodo esterno. Alla fine, restituisce un dizionario che rappresenta l'albero di copertura minimo.

Entrambi gli algoritmi, Dijkstra e Prim, sono utilizzati per trovare soluzioni ottimali in contesti specifici dei grafi. Mentre l'algoritmo di Dijkstra è specializzato nel trovare i percorsi più brevi in un grafo pesato, l'algoritmo di Prim si concentra sulla costruzione di un albero di copertura minimo.

Questi algoritmi sono solo alcuni esempi della vasta gamma di strumenti disponibili per la manipolazione e l'analisi dei grafi. La scelta dell'algoritmo dipende dal tipo di problema e dalle caratteristiche specifiche della rete o del grafo su cui si lavora.


Nessun commento:

Posta un commento