1
Fork 0
mirror of https://github.com/Steffo99/appunti-magistrali.git synced 2024-11-22 02:44:17 +00:00
appunti-steffo/2 - Algoritmi e strutture dati/1 - Appunti/21 - Grafo.md

145 lines
5.4 KiB
Markdown
Raw Normal View History

# Grafo
Un _grafo_ è una struttura dati che rappresenta elementi interconnessi tra loro.
Esistono due tipi di grafi: _orientati_ e _non orientati_.
Per semplicità, consideriamo i nostri nodi numerati da 1 a `n`.
## Proprietà
- Gli elementi sono rappresentati tramite _nodi_.
- Il loro _grado_ è dato dal **numero degli archi** che vi incidono.
- Se il grafo è orientato, hanno anche un _in-degree_ (**numero di archi entranti**) e un _out-degree_ (**numero di archi uscenti**).
- Le connessioni tra elementi sono rappresentate tramite _archi_.
- Un arco _incide_ esattamente su **due nodi**.
- Se il grafo è orientato, sono _uscenti_ da uno dei due nodi ed _entranti_ nell'altro.
- Sono matematicamente meno del **quadrato dei nodi**.
## Grafi particolari
### Catena
Una _catena_ è un **grafo non orientato** composto da una **sequenza di nodi** aventi un **grado massimo di 2** tutti collegati tra loro.
### Cammino
Un _cammino_ è un **grafo orientato** composto da una **sequenza di nodi** aventi un **in-degree** e un **out-degree** **massimo di 1**, collegati tra loro in modo che partendo dal primo e seguendo gli archi sia possibile arrivare all'ultimo.
### Cricca
Una _cricca_ è un grafo in cui **tutti i nodi sono collegati tra loro**.
Se il grafo è **non orientato**, la cricca ha `((n-1)n)/2` archi.
Se il grafo è **orientato**, ha per ogni coppia un arco in entrambe le direzioni, quindi ha `(n-1)n` archi.
### Direct Acyclic Graph
Un _DAG_ è un grafo diretto che non contiene nessun ciclo.
Su di esso possiamo effettuare un ordinamento, detto _linearizzazione_, tra i nodi: otteniamo l'_ordine topologico_.
I primi elementi dei DAG sono detti _Source_ (_Sorgente_), mentre gli ultimi sono detti _Sink_ (_Pozzo_).
#### Albero
Un **albero** può essere considerato un DAG con una **sorgente singola** e le **foglie come pozzi**.
### Grafo fortemente connesso
Un insieme di nodi `V` di un **grafo diretto** `G` si dice una _componente fortemente connessa_ se:
1. Per ogni coppia di nodi `∀ u, v ∈ V' : ∃ un cammino u->v in G'`
2. Massimale (non può diventare più grande)
> Praticamente una componente fortemente connessa è un gruppo di nodi tra i quali si può viaggiare liberamente da e a qualsiasi nodo al suo interno.
Un grafo si dice _fortemente connesso_ se l'insieme `V` coincide con l'insieme dei nodi del grafo `G`.
> Se partendo da qualsiasi nodo di un grafo riesco ad arrivare a qualsiasi altro nodo, allora il grafo è fortemente connesso.
Inoltre, se creiamo un nuovo grafo, in cui **ogni nodo rappresenta una componente fortemente connessa** del nostro grafo iniziale, **otteniamo un DAG**, perchè tutti i cicli sono stati integrati nella componente.
### Trasposto di un grafo
Il _trasposto_ di un **grafo diretto** `G` è il grafo stesso con gli archi che però vanno nella **direzione opposta**.
### Grafo pesato
Un _grafo pesato_ è un particolare grafo che associa a ciascun arco un **costo** per attraversarlo.
#### Costi negativi
I costi possono anche essere negativi: rappresenteranno allora un **guadagno** ottenuto attraversando il nodo.
### Minimum spanning tree
Un _minimum spanning tree_ è il **sottoinsieme degli archi** di un **grafo non diretto** che **connettono tutti i nodi** con il **minor costo possibile**.
I MST hanno [molte proprietà](https://en.wikipedia.org/wiki/Minimum_spanning_tree#Properties); sono troppe da scrivere qui, e probabilmente non ci interesseranno nemmeno.
## Implementazione tramite matrice di adiacenza
Possiamo implementare un grafo creando una **matrice di `bool` di dimensione `n * n`** in cui le **caselle collegate sono vere** e le caselle non collegate sono false.
> Ad esempio, possiamo implementare un grafo non orientato in questo modo (`█` indica l'esistenza di un collegamento e ` ` indica la sua assenza):
>
> | |1|2|3|
> |-|-|-|-|
> |1|░|░|░|
> |2|█|░|░|
> |3|█| |░|
>
> Esistono gli archi `1-2` e `1-3`, ma non esiste un collegamento `2-3`.
> Un grafo orientato invece si può implementare così:
>
> | |1|2|3|
> |-|-|-|-|
> |1|░|█| |
> |2|█|░| |
> |3|█| |░|
>
> Esistono gli archi `1->2`, `2->1` e `3->1`, ma non ci sono collegamenti `2->3`, `1->3` e `3->2`.
### Costo computazionale
#### Tempo
Le matrici di adiacenza portano alla realizzazione di algoritmi molto veloci: verificare l'esistenza di un arco è in `O(1)`!
Abbiamo però penalità significative quando vogliamo effettuare operazioni sugli archi: ad esempio, trovare il trasposto di un grafo implementato con una matrice di adiacenza è in `O(nodi²)`.
#### Memoria
E' poco efficiente in quanto a memoria: l'upper bound è in `O(n^2)`.
## Implementazione tramite liste di adiacenza
Un'alternativa alla matrice di adiacenza è quella di creare un'**array di liste**, le quali contengono i **vicini di ciascun nodo**.
> |Posizione|Lista|
> |-|-|
> |1|[2, 3]|
> |2|[]|
> |3|[1]|
>
> Esistono gli archi `1->2`, `1->3`, e `3->1`, ma non esistono `2->1`, `2->3` e `3->2`.
### Costo computazionale
#### Tempo
Utilizzando le liste di adiacenza, il tempo richiesto per verificare l'esistenza di un arco sale a `O(max-out-degree)`.
E' efficace però quando il problema che vogliamo risolvere riguarda operazioni su archi: trovare la trasposta è in `O(archi)`.
#### Memoria
La memoria richiesta dalle liste di adiacenza è minore di quella delle matrici: l'upper bound è in `O(nodi + archi)`.
## Visualizzazione
[visualgo.net](https://visualgo.net/en/graphds)