T - Grafi Flashcards
Definizione di ADT Grafo statico, semi-statico, dinamico.
- statico: non cambia
- semi-statico: si possono cancellare solo gli archi
- dinamico: si può cancellare tutto
Rappresentazioni possibili degli archi: descrizione, complessità spaziale, vantaggi e svantaggi.
1) Matrice di adiacenza: è una matrice adj |V| x |V| in cui l’elemento (i, j) vale 1 (o il peso nel caso di un grafo pesato) se l’arco (i, j) appartiene ad E.
È una matrice simmetrica se il grafo è non orientato.
È svantaggioso perché la complessità spaziale è S(n) = Theta(|V|^2).
È vantaggiosa perché non ci sono costi aggiuntivi per i grafi pesati e perché la lettura è diretta, O(1).
2) Lista di adiacenza: è un vettore A di |V| elementi in cui ogni elemento A[i] contiene il puntatore alla lista dei vertici adiacenti a i.
Gli elementi complessivi nelle liste sono al massimo 2|E| per i grafi non orientati e |E| per quelli orientati, per cui S(n) = O(max(|V|, |E|)) = O(|V + E|), e per questo è vantaggioso.
È svantaggioso perché c’è bisogno di un campo aggiuntivo per i pesi e perché la ricerca comporta la scansione della lista.
Come sono organizzati i file per l’elaborazione di un grafo? E le strutture dati? (da aggiornare con le altre slide dei grafi)
Bisogna lavorare con il main (client), con i file per i grafi e con quelli per la conversione indice_vertice <–> nome_vertice
main.c
#include
Graph.h
#include
typedef struct { int v; int w; int wt; } Edge;
typedef struct graph *Graph;
Graph.c
#include
// se si usa la lista delle adiacenze:
typedef struct node* link;
struct node {int v; int wt; link next; };
struct graph { int V; int E; link *ladj; ST tab; link z; };
// mentre se si usa la matrice delle adiacenze:
struct graph { int V; int E; int **mat; ST tab; };
ST.h
typedef struct symbtab *ST;
ST.c
struct symbtab {char **a; int MAXN; int N; }
GRAFI ORIENTATI E NON, LISTA DELLE ADIACENZE (pesati e non)
Scrivere il codice delle seguenti funzioni:
- Graph GRAPHinit(int V);
- void GRAPHfree(Graph G);
Graph.h
typedef struct edge {int v; int w; //int wt; } Edge;
typedef struct graph *Graph;
Graph GRAPHinit(int V);
void GRAPHfree(Graph G);
Graph.c
typedef struct node * link;
struct node {int v; //int wt; link next; };
struct graph {int V; int E; link *ladj; ST tab; link z; };
Graph GRAPHinit(int V){
Graph g = malloc(sizeof(*g));
if (g == NULL) return NULL;
g->V = V;
g->E = 0;
g->z = NEW(-1, 0, NULL);
g->ladj = malloc(V *sizeof(link));
if (g->ladj == NULL) return NULL;
for (int i = 0; i < g->V; i++){
g->ladj[i] = z;
}
g->tab = STinit(V);
return g;
}
void GRAPHfree(Graph G){
link t;
for (int i = 0; i < G->V; i++){
for (link x = G->ladj[i]; x != G->z; x = t){
t = x->next;
free(x);
}
}
free(G->ladj);
STfree(tab);
free(z);
free(G);
}
GRAFI ORIENTATI E NON, LISTA DELLE ADIACENZE (pesati e non)
Scrivere il codice delle seguenti funzioni:
- Graph GRAPHload(FILE *fin);
Graph.h
typedef struct edge {int v; int w; //int wt; } Edge;
typedef struct graph *Graph;
Graph GRAPHload(FILE *fin);
Graph.c
typedef struct node * link;
struct node {int v; //int wt; link next; };
struct graph {int V; int E; link *ladj; ST tab; link z; };
Graph GRAPHload(FILE *fin){
Graph G;
int V, i, wt;
char label1[MAX], label2[MAX];
fscanf(fin, “%d”, &V);
G = GRAPHinit(V);
for (i = 0; i < V; i++){
fscanf(fin, “%s”, label1);
STinsert(G->tab, label1, i);
}
while (fscanf(fin, “%s %s %d”, label1, label2, &wt) == 3){
id1 = STsearch(G->tab, label1);
id2 = STsearch(G->tab, label2);
if (id1 >= 0 && id2 >=0) GRAPHinsertE(G, id1, id2, wt);
}
}
void GRAPHinsertE(Graph G, int id1, int id2, int wt) {
insertE(G, EDGEcreate(id1, id2, wt));
}
static Edge EDGEcreate(int v, int w, int wt) {
Edge e;
e.v = v; e.w = w; e.wt = wt;
return e;
}
// NON ORIENTATI
static void insertE(Graph G, Edge e) {
int v = e.v, w = e.w, wt = e.wt;
G->ladj[v] = NEW(w, wt, G->ladj[v]);
G->ladj[w] = NEW(v, wt, G->ladj[w]);
G->E++;
}
//ORIENTATI
static void insertE(Graph G, Edge e) {
int v = e.v, w = e.w, wt = e.wt;
G->ladj[v] = NEW(w, wt, G->ladj[v]);
G->E++;
}
GRAFI ORIENTATI E NON, LISTA DELLE ADIACENZE (pesati e non)
Scrivere il codice delle seguenti funzioni:
- void GRAPHstore(Graph G, FILE *fout);
- void GRAPHedges(Graph G, Edge *a);
Graph.h
typedef struct edge {int v; int w; //int wt; } Edge;
typedef struct graph *Graph;
Graph GRAPHload(FILE *fin);
Graph.c
typedef struct node * link;
struct node {int v; //int wt; link next; };
struct graph {int V; int E; link *ladj; ST tab; link z; };
void GRAPHstore(Graph G, FILE *fout) {
int i;
Edge *a;
a = malloc(G->E * sizeof(Edge));
if (a == NULL) return;
GRAPHedges(G, a);
fprintf(fout, “Numero vertici: %d\n”, G->V);
for (i = 0; i < G->V; i++)
fprintf(fout, “%s\n”, STsearchByIndex(G->tab, i));
for (i = 0; i < G->E; i++)
fprintf(fout, “%s %s %d\n”, STsearchByIndex(G->tab, a[i].v),
STsearchByIndex(G->tab, a[i].w), a[i].wt);
}
// NON ORIENTATI
void GRAPHedges(Graph G, Edge *a) {
int v, E = 0;
link t;
for (v=0; v < G->V; v++)
for (t=G->ladj[v]; t != G->z; t = t->next)
if (v < t->v)
a[E++] = EDGEcreate(v, t->v, t->wt);
}
// ORIENTATI
void GRAPHedges(Graph G, Edge *a) {
int v, E = 0;
link t;
for (v=0; v < G->V; v++)
for (t=G->ladj[v]; t != G->z; t = t->next)
a[E++] = EDGEcreate(v, t->v, t->wt);
}
GRAFI ORIENTATI E NON
Codice delle funzioni:
- int GRAPHgetIndex(Graph *G, char * label);
- void GRAPHremoveE(Graph G, int id1, int id2); (sia lista che matrice)
int GRAPHgetIndex(Graph G, char * label){
int id;
id = STsearch(G->tab, label);
if (id == -1) {
id = STsize(G->tab);
STinsert(G->tab, label, id);
}
return id;
}
void GRAPHremoveE(Graph G, int id1, int id2) {
removeE(G, EDGEcreate(id1, id2));
}
static void removeE(Graph G, Edge e) {
int v = e.v, w = e.w;
link x;
if (G->ladj[v]->v == w) {
G->ladj[v] = G->ladj[v]->next;
G->E–;
}
else
for (x = G->ladj[v]; x != G->z; x = x->next)
if (x->next->v == w) {
x->next = x->next->next;
G->E–;
}
// nei grafi orientati non c’è questa seconda parte
if (G->ladj[w]->v == v)
G->ladj[w] = G->ladj[w]->next;
else
for (x = G->ladj[w]; x != G->z; x = x->next)
if (x->next->v == v)
x->next = x->next->next;
}
static void removeE(Graph G, Edge e) {
int v = e.v, w = e.w, int wt = e.wt;
if (G->madj[v][w] != maxwt)
G->E–;
G->madj[v][w] = maxwt;
G->madj[w][v] = maxwt; // solo grafi non orientati
}
}
GRAFI ORIENTATI E NON, MATRICE DELLE ADIACENZE (pesati e non)
Scrivere il codice delle seguenti funzioni:
- Graph GRAPHinit(int V);
- void GRAPHfree(Graph G);
Graph.h
#include
typedef struct graph * Graph;
typedef struct {int v; int w; int wt; } Edge;
Graph.c
#include
#define maxwt INT_MAX
struct graph { int V; int E; int **madj; ST tab; };
static int **MATRIXint(int r, int c, int val);
static int MATRIXint(int r, int c, int val){
int i, j;
int **m = malloc(rsizeof(int));
if (m == NULL) return NULL;
for (i = 0; i < r; i++){
m[i] = malloc(c*sizeof(int);
if (m[I] == NULL) return NULL;
}
for (i = 0; i < r; i++)
for(j = 0; j < c; j++)
m[i][j] = val;
return m;
}
Graph GRAPHinit(int V){
Graph G = malloc(sizeof(*G));
if (G == NULL) return NULL;
G->V = V;
G->E = 0;
G->madj = MATRIXint(V, V, maxwt);
if (G->madj == NULL) return NULL;
G->tab = STinit(V);
if (G->tab == NULL) return NULL;
return G;
}
void GRAPHfree(Graph G){
int i, j;
for (i = 0; i < G->V; i++) free(G->madj[i]);
free(G->madj);
STfree(G->tab);
free(G);
}
GRAFI ORIENTATI E NON, MATRICE DELLE ADIACENZE (pesati e non)
Scrivere il codice delle seguenti funzioni:
- Graph GRAPHload(FILE *fin);
Graph GRAPHload(FILE *fin){
int V, id1, id2, wt;
char label1[MAX], label2[MAX];
fscanf(“%d”, &V);
Graph G = GRAPHinit(V);
for (int i = 0; i < V; i++){
fscanf(“%s”, label1);
STinsert(G->tab, label1, i);
}
while(fscanf(“%s %s %d”, label1, label2, &wt) == 3){
id1 = STsearch(G->tab, label1);
id2 = STsearch(G->tab, label2);
if(id1≥0 && id2≥0) GRAPHinsertE(G, id1, id2, wt);
}
return G;
}
void GRAPHinsertE(Graph G, int id1, int id2, int wt) {
insertE(G, EDGEcreate(id1, id2, wt));
}
static Edge EDGEcreate(int v, int w, int wt){
Edge e;
e.v = v; e.w = w; e.wt = wt;
return e;
}
static void insertE(Graph G, Edge e) {
int v = e.v, w = e.w, wt = e.wt;
if (G->madj[v][w] == maxWT) G->E++;
G->madj[v][w] = wt;
G->madj[w][v] = wt; // solo per grafi non orientati
}
GRAFI ORIENTATI E NON, MATRICE DELLE ADIACENZE (pesati e non)
Scrivere il codice delle seguenti funzioni:
- void GRAPHstore(Graph G, FILE *fout);
- void GRAPHedges(Graph G, Edge *a);
void GRAPHstore(Graph G, FILE *fout) {
int i;
Edge *a = malloc(G->E * sizeof(Edge));
if (a == NULL) return;
GRAPHedges(G, a);
fprintf(fout, “Numero vertici: %d\n”, G->V);
for (i = 0; i < G->V; i++)
fprintf(fout, “%s\n”, STsearchByIndex(G->tab, i));
for (i = 0; i < G->E; i++)
fprintf(fout, “%s %s %d\n”, STsearchByIndex(G->tab, a[i].v),
STsearchByIndex(G->tab, a[i].w), a[i].wt);
}
void GRAPHedges(Graph G, Edge *a) {
int v, w, E = 0;
for (v=0; v < G->V; v++)
for (w=v+1; w < G->V; w++)
if (G->madj[v][w] != 0)
a[E++] = EDGEcreate(v, w, G->madj[v][w]);
}
// ORIENTATI
void GRAPHedges(Graph G, Edge *a) {
int v, w, E = 0;
for (v=0; v < G->V; v++)
for (w=0; w < G->V; w++)
if (G->madj[v][w] != 0)
a[E++] = EDGEcreate(v, w, G->madj[v][w]);
}
Quali strategie abbiamo studiato per la visita di un grafo?
- Depth First Search
- Breadth First Search
A cosa quali tipi di grafo è applicabile la visita DFS? Cosa genera?
La DFS è applicabile a grafi connessi e non.
Genera un albero della visita in profondità.
DFS: Cosa si intende con profondità?
Con profondità si intende l’espansione dell’ultimo vertice scoperto che ha ancora vertici non ancora scoperti a lui adiacenti.
DFS: Come vengono trattati i vertici?
La DFS parte da un vertice e visita tutti i vertici del grafo, sia che siano da essi raggiungibili che non.
Ogni vertice viene etichettato con il tempo di scoperta e con il tempo di fine elaborazione: pre[i] e post[i]
Per ogni vertice viene memorizzato il padre, ovvero il vertice a partire dal quale può essere raggiunto.
I vertici vengono rappresentati concettualmente come bianchi, grigi e neri.
DFS: come vengono etichettati gli archi?
Ogni arco può essere etichettato come T, B, F, C (solo T e B se il grafo è non orientato).
- T, tree: è il tipo di arco che si genera nella ricorsione
- B, backward: è l’arco che connette un vertice j a un suo antenato i per cui vale che post[i] == -1 ovvero non è ancora stato completato
- F, forward: connette un vertice i a un suo discendente j per cui si ha che pre[i] < pre[j]
- C: sono gli archi rimanenti
Scrivere il codice della void GRAPHdfs(Graph G, int id);(solowrapper)
void GRAPHdfs(Graph G, int id) {
int v, time=0, *pre, *post, *st;
pre = malloc(G->V * sizeof(int));
post = malloc(G->V * sizeof(int));
st = malloc(G->V * sizeof(int));
if ((pre == NULL) || (post == NULL) || (st == NULL)) return;
for (v=0; v < G->V; v++) {
pre[v] = -1; post[v] = -1; st[v] = -1;}
dfsR(G, EDGEcreate(id, id), &time, pre, post, st);
for (v=0; v < G->V; v++)
if (pre[v]== -1) dfsR(G, EDGEcreate(v,v), &time, pre, post, st);
printf(“discovery/endprocessing time labels \n”);
for (v=0; v < G->V; v++)
printf(“vertex %s : %d/%d \n”, STsearchByIndex(G->tab, v), pre[v],
post[v]);
printf(“resulting DFS tree \n”);
for (v=0; v < G->V; v++)
printf(“parent of vertex %s is vertex %s \n”, STsearchByIndex(G->tab,
v), STsearchByIndex(G->tab, st[v]));
}
Scrivere il codice della static void dfsR(Graph G, Edge e, int *time, int *pre, int *post, int *st);
static void dfsR(Graph G, Edge e, int *time, int *pre, int *post, int *st) {
link t;
int v, w = e.w;
Edge x;
if (e.v != e.w)
printf(“edge (%s, %s) is tree \n”, STsearchByIndex(G->tab, e.v),
STsearchByIndex(G->tab, e.w)) ;
st[e.w] = e.v;
pre[w] = (time)++;
for (t = G->ladj[w]; t != G->z; t = t->next)
if (pre[t->v] == -1)
dfsR(G, EDGEcreate(w, t->v), time, pre, post, st);
else {
v = t->v;
x = EDGEcreate(w, v);
if (post[v] == -1)
printf(“edge (%s, %s) is back \n”, STsearchByIndex(G->tab, x.v),
STsearchByIndex(G->tab, x.w));
else
if (pre[v]>pre[w])
printf(“edge (%s, %s) is forward \n”, STsearchByIndex(G->tab,
x.v), STsearchByIndex(G->tab, x.w));
else
printf(“edge (%s, %s) is cross \n”, STsearchByIndex(G->tab, x.v),
STsearchByIndex(G->tab, x.w));
}
post[w] = (time)++;
}
DFS: lista delle adiacenze
Complessità di inizializzazione, visita ricorsiva, tempo (lista e matrice)
- inizializzazione: Theta(V)
- visita ricorsiva: Theta(E)
- lista: T(n) = Theta(V+E)
- matrice: T(n): Theta(V^2)
BFS: a che tipo di visita porta
Dato un vertice di partenza, si visitano solo quelli da esso raggiungibili
BFS: cosa calcola di fatto?
Calcola la distanza minima dal vertice di partenza
BFS: cosa genera?
Un albero di visita in ampiezza
BFS: trattamento vertici
Ogni vertice viene etichettato con il tempo di scoperta pre[i] e per ogni vertice viene indicato il padre.
BFS: quale struttura dati viene utilizzata? Cosa permette?
Viene utilizzato un ADT di I classe coda e in base a come viene implementata varia la complessità.
La coda è una modalità seriale che permette di emulare un processo di visita dei vertici parallelo.
BFS: come vengono visitati i vertici?
In modo parallelo.
Codice della void GRAPHbfs(Graph G, int id); (solo wrapper)
void GRAPHbfs(Graph G, int id) {
int v, time=0, pre, st;
pre = malloc(G->Vsizeof(int));
st = malloc(G->Vsizeof(int));
if ((pre == NULL) || (st == NULL)) return;
for (v=0; v < G->V; v++) {
pre[v] = -1; st[v] = -1;
}
bfs(G, EDGEcreate(id,id), &time, pre, st);
printf(“\n Resulting BFS tree \n”);
for (v=0; v < G->V; v++)
if (st[v] != -1)
printf(“%s’s parent is: %s \n”, STsearchByIndex(G->tab, v),
STsearchByIndex(G->tab, st[v]));
}
Codice della static void bfs(Graph G, Edge e, int *time, int *pre, int *st);
static void bfs(Graph G, Edge e, int *time, int *pre, int st) {
int v;
Q q = Qinit();
Qput(q, e);
while (!Qempty(q))
if (pre[(e = Qget(q)).w] == -1) {
pre[e.w] = (time)++;
st[e.w] = e.v;
for (v = 0; v < G->V; v++)
if (G->madj[e.w][v] == 1)
if (pre[v] == -1)
Qput(q, EDGEcreate(e.w, v));
}
Qfree(q);
}
BFS: complessità della scansione nel caso della lista e della matrice
- lista: T(n) = O(V+E)
- matrice: T(n) = Theta(V^2)
APPLICAZIONI DEGLI ALGORITMI DI VISITA DEI GRAFI: Quando G è aciclico?
Quando non si incontrano archi di tipo B in una DFS
APPLICAZIONI DEGLI ALGORITMI DI VISITA DEI GRAFI: che tipo di implementazione si utilizza per la ricerca di CC in un grafo? In che tipo di grafo si cercano le CC?
Le componenti connesse si cercano in un grafo non orientato e per la ricerca si utilizza l’implementazione con la lista delle adiacenze
APPLICAZIONI DEGLI ALGORITMI DI VISITA DEI GRAFI: algoritmo concettuale per la ricerca di CC
In base a un indice si cerca per ogni vertice la componente connessa a cui appartiene (vettore int *cc). Se alla fine c’è una sola componente il grafo è connesso
APPLICAZIONI DEGLI ALGORITMI DI VISITA DEI GRAFI: codice della int GRAPHcc(Graph G);
int GRAPHcc(Graph G){
int v, id = 0, cc;
cc = malloc(G->Vsizeof(int));
if (cc == NULL) return -1;
for (v = 0; v < G->v; v++) cc[v] = -1;
for (v = 0; v < G->v; v++) {
if(cc[v] = -1) dfsRcc(G, v, id++, cc);
printf(“Componenti connesse: \n”);
for (v = 0; v < G->V; v++){
printf(“Il nodo %s appartiene alla cc %d \n”, STsearchbyindex(G->tab, v), cc[v]);
}
return id;
}
static void dfsRcc(Graph G, int v, int id, int *cc){
cc[v] = id;
for(link t = G->ladj[v]; t != G->z; t = t->next){
if (cc[t->v] == -1) dfsRcc(G, t->v, id, cc);
}
}
APPLICAZIONI DEGLI ALGORITMI DI VISITA DEI GRAFI: cosa vuol dire cercare la connettività? Su che tipo di grafo si cerca?
Cerchiamo la connettività su un grafo non orientato e connesso. Studiare la connettività vuol dire studiare i vertici e gli archi per i quali, se eliminati, il grafo si sconnette: sono i punti di articolazione e i ponti.
APPLICAZIONI DEGLI ALGORITMI DI VISITA DEI GRAFI: definizione di punto di articolazione
Un punto di articolazione è un punto che se eliminato sconnette il grafo.
Una radice lo è se ha almeno due figli.
Un vertice generico v lo è se v ha un figlio s e da s o da un suo discendente NON parte un arco B che va a un antenato proprio di v (se l’arco c’è rimane tutto collegato e non si sconnette nulla).
APPLICAZIONI DEGLI ALGORITMI DI VISITA DEI GRAFI: definizione di ponte
Un ponte è un arco che se eliminato sconnette il grafo.
Un arco B non è mai un ponte.
Un arco T (v, w) lo è se non ci sono archi B che connettano un discendente di w a un antenato di v.
DAG: caratteristiche
Un DAG è un grafo orientato e aciclico.
È caratterizzato da due classi di nodi: sorgente e pozzo. I nodi sorgente hanno in_degree = 0, i nodi pozzo hanno out_degree = 0.
Ogni DAG ha sempre almeno un ordinamento topologico
DAG: per cosa viene usato tipicamente?
Viene utilizzato per gli ordinamenti parziali -> problemi di scheduling
DAG: quali sono le operazioni tipiche?
Le operazioni tipiche sono gli ordinamenti topologici e gli ordinamenti tipologici inversi.
- ordinamenti topologici: riordinamento dei vertici secondo una linea orizzontale per cui dato un arco (u, v) si ha che sulla linea u sarà alla sx di v e quindi tutti gli archi andranno da sx a dx
- ordinamenti topologici inversi: archi da sx a dx quindi per (u, v) si avrà che u sarà alla destra di v
DAG: quando un ordinamento è unico?
Un ordinamento è unico quando esiste un cammino di Hamilton orientato: cammino semplice che tocca tutti i vertici
DAG: struttura dati per l’ordinamento topologico inverso
Viene utilizzato un grafo ADT di I classe implementato con la lista delle adiacenze.
- *pre: per ogni VERTICE viene indicato il tempo di scoperta
- *ts: per ogni TEMPO viene registrato quale vertice viene completato a quel tempo
- contenitore time: (contenitore ma di fatto è un int) per i tempi di ordinamento, avanza solo quando un vertice è completato
DAG: codice della void DAGrts(Dag D);
void DAGrts(Dag D){
int v, time = 0, *pre, *ts;
// allocazione, controllo e inizializzazione a -1
for (v = 0; v < D->V; v++)
if (pre[v] == -1) TSdfsR(D, v, ts, pre, &time);
printf(“Vertici del DAG in ordine topologico inverso: \n”);
for (v = 0; i < D->V; v++)
printf(“%s “, STsearcchbyIndex(D->tab, ts[v]));
}
static void TSdfsR(Dag D, int v, int *pre, int *ts, time){
link t;
pre[v] = 0;
for (t = D->ladj[v]; t != D->z; t. =t->next){
if (pre[t->v] == -1) TSdfsR(D, t->v, pre, ts, time);
}
ts[(time)++] = v;
}
DAG: per l’ordinamento topologico quale implementazione del grafo si usa?
Grafo ordinato con matrice delle adiacenze.
DAG: concettualmente come si realizza l’ordinamento topologico inverso?
SI invertono i riferimenti riga-colonna.