Definició i propietats
Graf:
\(\begin{array}{lll}G: & \small V\grave{e}rtexs/Nodes & V = \{1, 2, 3, 4\} \\ & \small Arestes & E = \left\{\{1, 2\}, \{1, 3\}, \{2, 3\}, \{3, 4\}\right\}\end{array}\)
Graf dirigit (digraf):
\(\begin{array}{lll}G': & \small V\grave{e}rtexs/Nodes & V = \{1, 2, 3, 4\} \\ & \small Arcs & E \subset VxV\ \small (un\ arc\ \acute{e}s\ un\ parell\ ordenat\ de\ v\grave{e}rtexs)\\ & & E = \left\{(1, 2), (1, 3), (2, 3), (3, 4)\right\}\end{array}\)
Un camí de longitud \(l\) és una seqüència de vèrtexs connectats (per arrestes/arcs) entre ells. Si tots els vèrtexs són diferents, el camí és simple. Un cicle és un camí simple amb la peculiaritat que comença i acaba al mateix vèrtex.
El diàmetre d'un graf és el màxim de tots els camins mínims.
Diem que un graf \(G = (V, E)\) és connex si per tot parell de vèrtexs \(u, v \in V\) existeix un camí que comença en \(u\) i acaba en \(v\).
Un graf no dirigit, connex i acíclic (sense cicles) és un arbre (arbre lliure); és un graf no dirigit on hi ha exactament un camí entre cada parell de vèrtexs. Els arbres tenen les següents propietats:
- un arbre amb \(n\) vèrtex té \(n – 1\) arestes;
- si s'afegeix una aresta a un arbre llavors tindrà un (únic) cicle;
- si s'elimina una aresta d'un arbre llavors deixa de ser connex.
Un graf dirigit és fortament connex si tots els parells de vèrtexs (considerant l'ordre) tenen un camí que els uneix, i dèbilment connex si és connex al convertir-lo en un graf no dirigit.
Un vèrtex \(v\) és adjacent al vèrtex \(u\) si \(\{u, v\} \in E\) (\((u, v) \in E\) si és un graf dirigit).
El grau d'un vèrtex és el nombre de vèrtexs adjacents a ell i el grau d'un graf és el grau màxim dels vèrtexs.
En grafs no dirigits, \(G = (V, E)\), es compleix el següent teorema: \(\sum_{u \in v}grau(u) = 2|E|\).
Un graf és complet si conté el màxim nombre d'arestes/arcs possible. En el cas d'un graf aquest és \(\frac{n(n-1)}{2}\) i en el cas d'un graf dirigit \(n(n-1)\).
Un graf connex i no dirigit és Eulerià si i només si existeix un camí que inclou cada aresta del graf exactament un cop.
Un graf és Hamiltonià si i només si conté un cicle Hamiltonià (un cicle simple de longitud \(|V|\)).
Un graf és planar si és possible representar-lo en paper sense que es creui cap línia. El teorema de Kuratowski afirma que només el grafs que no contenen \(k_{3,3}\) (el graf complet bipartit de 6 vèrtexs) ni \(K_5\) (el graf complet de 5 vèrtexs) són planars. Enllaç: article a la Viquipèdia.
Els grafs també poden tenir les següents característiques, que no utilitzarem en aquest curs:
- Bucle (loop): aresta/arc que comença i acaba en el mateix vèrtex.
- Multigrafs: graf amb més d'una aresta/arc connectant el mateix parell de vèrtexs.
Implementació
El tipus abstracte de dades (TAD) graf compta amb mètodes com crea, inserta_aresta, compta_vertex, etc. Les dades es poden representar de diverses maneres.
Matriu d'ajdacència
Consisteix en representar el graf en una matriu de booleans \(n x n\). Per a grafs no dirigits i sense bucles, és una matriu triangular, simètrica en els dos costats.
… [FIXME: aquí van dues imatges d'exemple i les matrius corresponents] …
- Espai: \(\theta(n^2)\)
- Temps de creació: \(\Omega(n^2)\) (\(\theta(n^2)\) més el temps de lectura del graf)
Llista d'adjacència
|
|
- Espai: \(\theta(n + m)\) on \(n\) és el nombre de vèrtex i \(m\) el nombre d'arestes.
Quina de les representacions és més convenient?
La matriu és més eficient en temps (accès directe a qualsevol posició en \(\theta(1)\)) però en espai pot ser molt ineficient (quan conté molts zeros, en el cas de grafs esparsos). Utilitzem la matriu per a grafs densos (\(|E| = \theta(n^2)\)).
Exemple
Tenim un graf amb \(n = 4182\) vèrtex i \(m = 12384\) arestes.
L'espai en memòria que aquest ocuparà en cas d'utilitzar una matriu d'adjacència, suposant que un booleà ocupa un byte, és \(\frac{4182^2}{2} \simeq 8.34MB\) (suposant que un booleà ocupa un bit, serien \(\frac{4182^2}{2 \cdot 8} = 1.04MB\)).
Si en lloc d'una matriu utilitzem una llista d'adjacència, on cada enter (o punter) ocupa 4 bytes, l'espai que ocuparà és \(4182 \cdot 4 + 12384 \cdot 4 \cdot 2 = 0.11MB\) (multipliquem per 2 al suposar que es tracta d'un graf no dirigit).
Recorreguts
Un recorregut és una manera sistemàtica de visitar tots els vèrtex d'un graf.
Recorregut en profunditat
El recorregut en profunditat (DFS, depth first search), similar al recorregut en pre-ordre dels arbres, consisteix en visitar un vèrtex i continuar el recorregut amb un vèrtex adjacent o l'últim vèrtex visitat que no hagi estat visitat fins ara.
list<int> dfs(const graf& G) { list<int> L; vector<bool> visitat(G.size(), false); stack<int> S; for (int i = 0; i < G.size(); ++i) { if (not visitat[i]) { S.push(i); while (not S.empty()) { int v = S.top(); S.pop(); L.push_back(v); visitat[v] = true; for (int w = 0; w < G[v].size(); ++w) { if (not visitat[G[v][w]]) S.push(G[v][w]); } } } } return L; } |
Recorregut en amplada
El recorregut en amplada (BFS, breadth first search) és similar al recorregut per nivells en un arbre. La implementació és similar a la del recorregut en profunditat, però utilitzant una cua en lloc d'una pila.
list<int> bfs(const graf& G) { list<int> L; vector<bool> encuat(G.size(), false); queue<int> Q; for (int i = 0; i < G.size(); ++i) { if (not encuat[i]) { Q.push(i); encuat[i] = true; while (not Q.empty()) { int v = Q.front(); Q.pop(); L.push_back(v); for (int w = 0; w < G[v].size(); ++w) { if (not encuat[G[v][w]]) { Q.push(G[v][w]); encuat[G[v][w]] = true; } } } } } return L; } |
Ordenació topològica
Donat un graf dirigit i acíclic (DAG, directed acyclic graph), \(G = \{V, E\}\), una ordenació topològica de \(G\) és una ordenació dels seus vèrtexs tal que per tot parell de vèrtexs \(u, v \in V\), si existeix un camí d'\(u\) a \(v\) al graf \(G\), aleshores \(v\) no pot aparèixer abans que \(u\) a l'ordenació.
Algorisme: Calculem el grau d'entrada (el número d'arcs entrants) de tots els nodes. Afegim aquells on el grau és zero en una pila i restem 1 al grau de tots els seus nodes adjacents; aquells adjacents que passin a grau 0 també els afegim a la pila.
Com podem veure, l'ordenació topològica és una aplicació del recorregut en profunditat. També és evident que no és única, ja que pot tenir molts resultats possibles:
Algorisme de Dijkstra
Un graf \(G = \{V, E\}\) és un graf amb pesos (weighted graph) si cada aresta \(\{u, v\}\) (o arc \((u, v)\)) del graf té associat un valor \(w \in \mathbb{R}\). Si \(w \in \mathbb{R}^{+}\), \(G\) és un graf amb pesos positius; si \(w \in \mathbb{R}^{+} \cup \{0\}\), és un graf amb pesos no negatius.
Donat un graf \(G\) amb pesos, el pes (també anomenat valor, cost o distància, si els pesos són positius) és la suma dels pesos de les arestes (o arcs) del camí.
Donats dos vèrtexs \(u, v \in V\) qualssevol, diem que un camí d'\(u\) a \(v\) és mínim (o de pes mínim) si el seu pes és més petit o igual que el pes de qualsevol altre camí d'\(u\) a \(v\). (Si no hi ha cap cami d'\(u\) a \(v\) diem, per definició, que el pes és \(\infty\)).
L'algorisme de Dijkstra (pronunciació: 'd3Ikstra) calcula els camins mínims d'un vèrtexs \(v\) cap a tots els altres vèrtexs del graf. Es tracta d'un algorisme voraç (greedy).
… [FIXME: aquí va un exemple] …
Observacions:
- L'algorisme de Dijkstra no funciona si hi ha pesos negatius.
- El seu cost és \(O(n^2)\).
Anàlisi de costs
Cost del recorregut en amplada
- Cada vèrtex del graf entra i surt de la cua exactament una vegada. Això correspon a un temps \(\theta(|V|)\).
- La llista d'adjacències de cada vèrtex es recorre una única vegada, en el moment de treure el vèrtex de la cua. Per tant, per cada vèrtex \(u\) això correspon a un temps \(\theta(Adj(u))\), i en total, per a tots els vèrtexs, això correspon a un temps \(\theta(\sum_{u \in V}Adj(u)) = O(|E|)\).
- Hi ha el cost de les inicialitzacions, que és \(O(n)\).
En total, el cost del recorregut en amplada és \(O(n + m) = O(|V| + |E|)\).
Cost del recorregut en profunditat
- El bucle exterior es fa \(\theta(|V|)\) vegades.
- Per cada iteració, el bucle interior es fa per tots els adjacents a cada vèrtex amb un cost \(\theta(1)\). En total, el cost és \(\theta(|E|)\).
- El cost de les inicialitzacions és \(O(n)\).
En total, el cost del recorregut en profunditat és \(\theta(|V| + |E|)\).
Cost de l'ordenació topològica
És igual al cost del recorregut en profunditat.
Cost de l'algorisme de Dijkstra
- Sense fer servir cues de prioritat:
Per a cada vèrtex que s'afegeix al conjunt solució el cost en temps és \(O(n)\) en cas pitjor. Com que s'hi han d'afegir els \(n\) vèrtexs del grup, en total el cost és \(O(n^2)\). - Fent servir cues de prioritat per guardar les arestes/arcs:
A cada pas afegim un nou element al conjunt solució, tret de la cua de prioritats, amb cost \(log n + (log n) Adj(v)\). En total: \(O((n + m) log n)\).
Enllaços rellevants
- ADA: Algorismes sobre grafs, per Gabriel Valiente.
- Article sobre grafs a la Viquipèdia.
No comments
© Siegfried-Angel Gevatter Pujals, 2011. |
Permalink |
License |
Post tags: algorithmics, eda, fib, universitat