Está en la página 1de 78

+ Grado en

Ingeniería
Tema 5 Informática

Grafos Estructuras
de Datos II

2018/19

Departamento de Tecnologías de la Información


Universidad de Huelva
+ 2

Grafos
Objetivos
Estudiar la especificación del TAD Grafo
Presentar diferentes alternativas de implementación
Conocer los algoritmos más importantes de manipulación de grafos

Contenidos
6.1 Conceptos
6.2 Especificación algebraica
6.3 Implementación
6.4 Recorridos sobre grafos
6.5 Ordenación topológica
6.6 Caminos mínimos sobre grafos: Algoritmos de Dijkstra y Floyd
6.7 Árbol de expansión de coste mínimo: Algoritmos de Prim y Kruskal
Estructuras de Datos II Tema 5 Grado en Ingeniería Informática
+ 3

Grafos
Duración
6 clases

Bibliografía
Data Structures and Algorithms in C++
Autores: Michael T. Goodrich, Roberto Tamassia, David M. Mount
Editorial: John Wiley & Sons, Inc.
Año: 2004
Págs. 575 – 656
Estructuras de datos y métodos algorítmicos
Autores: N. Martí, Y. Ortega, J.A. Verdejo
Editorial: Prentice-Hall, 2004
Págs. 231 – 276, 381 – 395
Fundamentos de Estructuras de Datos. Soluciones en Ada, Java y C++
Autores: Zenón J. Hernández Figueroa y otros
Editorial : Thomson, 2005
Págs. 415 – 455

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+
6.1 Conceptos

4
+ 5

Conceptos

Un grafo es una estructura capaz de representar relaciones complejas entre objetos de un


mismo tipo. Formalmente se representa mediante el par G = (V, A), donde:
V es un conjunto de objetos llamados vértices o nodos
A es un conjunto de objetos denominados aristas o arcos
Las aristas representan relaciones entre los vértices, de forma que una arista es
un par (u, v) de vértices de V
Básicamente, podemos clasificar los grafos en 4 tipos dependiendo de dos criterios:
Grafo dirigido. Es aquel cuyas aristas forman pares ordenados (u v)
Grafo no dirigido. Es aquel cuyas aristas son pares no ordenados
Grafo etiquetado o valorado. Cuando se asocia información a cada arista de un grafo
Grafo no etiquetado. Cuando no se asocia ninguna información a las aristas
La teoría de grafos se aplica a campos tan diversos como química, geografía, ingeniería
eléctrica, comunicaciones, etc.
Estructuras de Datos II Tema 5 Grado en Ingeniería Informática
+ 6

Conceptos
Un ejemplo de grafo podría ser la red de metro de Madrid, donde los vértices
representan las estaciones y las aristas la línea que une dos estaciones:

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 7

Conceptos
Otro ejemplo de aplicación de los grafos es la construcción de modelos donde se
estudian las relaciones de precedencia que existen entre las tareas que se
necesitan para completar un trabajo (grafos PERT)

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 8

Conceptos

Algunas definiciones sobre grafos:

Un camino en un grafo G = (V, A) es una secuencia de vértices v1, ..., vn ∈ V,


con n ≥ 1, tal que (vi, vi+1) ∈ A, para i = 1, ..., n-1

La longitud de un camino es su número de vértices menos 1

Un camino es simple si todos sus vértices son distintos

Un ciclo es un camino de longitud no nula que empieza y termina en el mismo


vértice

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 9

Conceptos
Algunas definiciones sobre grafos:

Un ciclo es simple si son distintos cada vértice y cada arista del ciclo, excepto
el primer y último vértices

En un grafo no dirigido, el grado de un vértice v es el número de aristas que


contiene a v

Se dice que el vértice y es sucesor o adyacente del vértice x si existe una


arista que tenga por origen a x y por destino a y, es decir, si la arista (x, y) ∈ A

Se dice que el vértice x es antecesor del vértice y si existe una arista que tenga
por origen a x y por destino a y, es decir, si la arista (x, y) ∈ A

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 10

Conceptos

En un grafo dirigido se distingue entre el grado de entrada y el grado de salida: el


grado de entrada de un vértice v es el número de aristas que llegan a v
(antecesores), y el grado de salida de un vértice v es el número de aristas que salen
de v (sucesores).

Ejemplo 1: Grafo no dirigido

Número de nodos:
v2 v3
Número de aristas:
V=
A=
Camino =
Ciclo =
Grado (v3) =
v1 v4 Adyacentes (v3) =

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 11

Conceptos

Ejemplo 2: Grafo dirigido

Número de nodos:
Número de aristas:
v2 V=
A=
Camino =
Ciclo =
GradoEnt (v2) =
GradoSal(v2)=
v1 v3 Adyacentes (v3) =
Antecesores (v3) =
Antecesores (v2) =

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 12

Conceptos
Un grafo no dirigido G es conexo si existe un camino entre cualquier par de
nodos que forman el grafo

v2 v4
v2 v3 v4

v3

v5
v5
v1
v1

Grafo No conexo Grafo conexo

v2 v3

Un grafo dirigido es fuertemente conexo si existe un


v1 v4
camino entre cualquier par de nodos que forman el grafo

v5

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+
6.2 Especificación Algebraica

13
+ 14

Especificación algebraica

Puesto que el grafo es un conjunto de vértices y aristas, la signatura del TAD grafo deberá
contener, al menos, las operaciones de añadir vértices y aristas a un grafo

El resto de las operaciones dependerá de la aplicación que se vaya a dar al TAD

Definiremos un TAD para los grafos dirigidos, con las operaciones básicas para su
construcción, así como las operaciones de cálculo de vértices adyacentes a uno dado y
las de pertenencia de arista y de vértice

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ Especificación algebraica 15

espec grafosDirigidos
usa booleanos, conjunto
parámetro formal
género vértice
operaciones
_ == _: vértice vértice booleano
_ ≠ _: vértice vértice booleano Gen (grafo) =
fpf
renombrar conjunto<vértice> por conjVértices
conjunto<(vértice, vértice)> por conjAristas Mod (grafo) =
género grafo
operaciones
gVacío: grafo Obs (grafo) =
+vértice: grafo vértice grafo
parcial +arista: grafo vértice vértice grafo
_ ∈ _ : vértice grafo booleano
( _, _ ) ∈ _ : vértice vértice grafo booleano
esVacío: grafo booleano
adyacentes: grafo vértice conjVértices
-vértice: grafo vértice grafo
-arista: grafo vértice vértice grafo
vértices: grafo conjVértice
aristas: grafo conjAristas

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 16

Especificación algebraica
dominios de definición g: grafo; v1,v2: vértice
+arista (g,v1,v2) está definido sólo si v1∈g ∧ v2∈g
ecuaciones g: grafo; v,v1,v2,v3,v4: vértice
+vértice (+vértice (g, v1), v2) =

+arista (+arista (g, v1, v2), v3, v4) =

+arista (+vértice (g, v1), v2, v3) =

v ∈ gVacío =
v1 ∈ +vértice (g, v2) =
v1 ∈ +arista (g, v2, v3) =

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 17

Especificación algebraica
(v1, v2) ∈ gVacío =
(v1, v2) ∈ +vértice (g, v3) =
(v1, v2) ∈ +arista (g, v3, v4) =

esVacío (gVacío) =
esVacío (+vértice (g, v)) =
esVacío (+arista (g, v1, v2)) =

adyacentes (gVacío, v) =
adyacentes (+vértice (g, v1), v2) =
adyacentes (+arista (g, v1, v2), v3) =

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 18

Especificación algebraica

-vértice (gVacío, v) =
-vértice (+vértice (g, v1), v2) =

-vértice (+arista (g, v1, v2), v3) =

-arista (gVacío, v1, v2) =


-arista (+vértice (g, v1), v2, v3) =
-arista (+arista (g, v1, v2), v3, v4) =

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 19

Especificación algebraica

vértices (gVacío) =
vértices (+vértice (g, v)) =
vértices (+arista (g, v1, v2)) =

aristas (gVacío) =
aristas (+vértice (g, v)) =
aristas (+arista (g, v1, v2)) =

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+
6.3 Implementación

20
+ 21

Implementación

Un Vértice es un objeto con un atributo de tipo genérico

template <typename T>


class Vertice {
public:
Vertice(const T& objeto);
const T& getObj() const;
void setObj(const T& objeto);
bool operator==(const Vertice<T>& v) const;
bool operator!=(const Vertice<T>& v) const;

private:
T obj;
};

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 22

Implementación
Una Arista es un objeto con tres atributos: dos vértices y opcionalmente una etiqueta

template <typename T, typename U>


class Arista {
public:
Arista(const T& vo, const T& vd);
Arista(const T& vo, const T& vd, const U& etiq);
const T& getOrigen() const;
const T& getDestino() const;
const U& getEtiqueta() const;
void setOrigen(const T& orig);
void setDestino(const T& dest);
void setEtiqueta(const U& etiq);
bool operator==(const Arista<T, U>& a) const;
bool operator!=(const Arista<T, U>& a) const;
private:
T origen;
T destino;
U etiqueta;
};

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 23

Implementación

Interfaz informal de la clase Grafo:

template <typename T, typename U>


class Grafo{
public:
Grafo();
int numVertices() const;
bool esVacio() const;
bool estaVertice(const T& v) const;
bool estaArista(const T& v1, const T& v2) const;
const Conjunto<Vertice<T> >& vertices() const;
const Conjunto<Arista<T, U> >& aristas() const;
const Conjunto<Vertice<T> >& adyacentes(const T& v) const;
const Conjunto<Vertice<T> >& antecesores(const T& v) const;
void insertarVertice(const T& v);
void insertarArista(const T& v1, const T& v2, const U& e = U())
throw (NoExisteVerticeExcepcion);
void eliminarVertice(const T& v);
void eliminarArista(const T& v1, const T& v2);
}

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 24

Implementación

Existen básicamente 3 formas de implementar los grafos: una estática y dos dinámicas. La
elección de cada una de ellas dependerá de las operaciones que se vayan a aplicar sobre
los vértices y las aristas

Matriz de Adyacencia

Sea G = (V, A) un grafo de n nodos, donde suponemos que los nodos V = {v1, v2,... , vn}
están ordenados y podemos representarlos por sus ordinales {1, 2,... , n}

La matriz de adyacencia para el grafo G es una matriz A de dimensión n x n de elementos


booleanos en la que:
A[i, j] = verdad, si y sólo si existe una arista en G que va del vértice i al vértice j
A[i, j] = falso, en caso contrario

En un grafo no dirigido, la matriz de adyacencia es simétrica y los elementos de su


diagonal son todos falsos

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 25

Implementación
vértices de tipo entero

El tipo necesario para representar un grafo mediante matriz de adyacencia es:

template <typename T, typename U>


class Grafo {
public:
// métodos de la interfaz informal
private:
enum { N = …};
bool m[N][N];
int nv;
}

Esta implementación sólo se puede usar si los vértices son de tipo entero
Esta representación es útil para aquellos problemas donde se necesite saber si existe una
arista entre dos vértices dados, ya que es de orden constante O(1)

El principal inconveniente es que se necesita un espacio de O(n2) aunque el grafo tenga muy
pocas aristas

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 26

Implementación
vértices de tipo T

Si los vértices no fueran valores enteros podría utilizarse un Diccionario para almacenar los
pares <vértice, índice del vértice>
Tipo T Entero

Los Diccionarios son tipos asociativos que permiten almacenar pares (clave, valor). En el
caso de los grafos, la clave sería el vértice y el valor su índice

Existen implementaciones de los tipos diccionarios que permiten obtener el valor asociado a
una clave en un tiempo O(1)

En este caso, la matriz de adyacencia para el grafo G es una matriz A de dimensión n x n de


punteros a aristas, en la que:
A[i, j] = Puntero a la arista (i, j), si existe en G una arista que va del vértice i al j
A[i, j] = Nulo, en caso contrario

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 27

Implementación
vértices de tipo T

La clase Grafo quedaría de la siguiente forma:

template <typename T, typename U>


class Grafo {
public:

// métodos de la interfaz informal

private:
enum { N = …};
Arista<T>* m[N][N];
int nv;
Diccionario<T, int> dVertices;
}

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 28

Implementación
Ejemplo de Grafo Dirigido y no Etiquetado

<CA, HU>
CA CO HU SE

Sevilla CA • • <CA, SE>


Huelva
CO • • • •
m=
HU • • •
<HU, SE>
SE •

Córdoba <SE, HU>


Cádiz
<SE, CA> <SE, CO>

- Las filas y columnas de m son los valores


asociados por el diccionario a cada vertice
(i.e.: <CA, 0>, <CO, 1>, etc.)
- Para hacer más clara la explicación, en
algunas matrices se indica directamente el
vértice como índice

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ Implementación 29

Ejemplo de Grafo no Dirigido y no Etiquetado


<CA, HU>
CA CO HU SE
Sevilla CA • • <CA, SE>
Huelva
CO • • •
m= <CO, SE>
HU • •
SE • <HU, SE>

<HU, CA>
Córdoba <SE, HU>
Cádiz
<SE, CA> <SE, CO>

- Para un grafo con aristas etiquetadas, la matriz se declararía de la forma: Arista<T, U>* m[N][N];
- Donde U es el parámetro de tipo para la etiqueta de las aristas

2
<CA, HU, 4>
CA CO HU SE
Sevilla CA • • <CA, SE, 2>
Huelva
CO • • • •
3 m=
5 HU • • •
<HU, SE, 2>
4 2 SE •

2 Córdoba <SE, HU, 3>


Cádiz
<SE, CA, 2> <SE, CO, 5>

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 30

Implementación

Algoritmo del método adyacentes:

Conjunto<Vertice <T> > adyacentes(const T& v) const


var
Conjunto<Vertice <T> > c;
int pos;
fvar
Inicio
if dVertices.observar(v, pos) entonces
para i = 1 hasta nv hacer
si m[pos][i] ≠ NULO entonces
c.poner(Vertice <T> (m[pos][i]->getDestino()))
fsi
fpara
fsi
devolver c
fin

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 31

Implementación

Listas de Adyacencia

Esta representación consiste en n listas, de forma que la lista i-ésima contiene los
vértices adyacentes al vértice i

Una posible representación podría ser:

template <typename T, typename U>


class Grafo{
public:
// métodos de la interfaz informal
private:
Lista<Vertice<T> > *G;
int nv;
Diccionario<T, int> dVertices;
}

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 32

Implementación Las posiciones de G son los valores


asociados por el diccionario a cada
vertice (i.e.: <A, 1>, <B, 2>, etc.)

G
A C
1 B

2 A C D

B
3
4
D E 5 B C

G
A D
1 B D

2 A E

E
3 E

A E
4
B C B C D
5

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 33

Implementación

Algoritmo del método adyacentes:

Conjunto<Vertice<T> > adyacentes(const T& v) const


var
Conjunto<Vertice<T> > c
Lista<Vertice<T> >::Iterador it
int pos
fvar
Inicio
if dVertices.observar(v, pos) entonces
it = G[pos].principio()
mientras it ≠ G[pos].final() hacer
c.poner(it.observar(G[pos]))
it.avanzar(G[pos])
fmientras
fsi
devolver c
fin

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 34

Implementación

Esta representación resulta útil cuando el número de vértices se conoce


previamente y permanece fijo durante la resolución del problema, pero resulta
ineficiente si necesitamos añadir o eliminar vértices en tiempo de ejecución

Por tanto, para un caso más general, podemos utilizar, en lugar de una tabla, una
lista enlazada para almacenar la información de los vértices

template <typename T, typename U> template <typename T>


class Grafo{ class Adyacencia{
public: public:
// métodos de la interfaz informal // métodos tipo get/set
private: private:
Lista<Adyacencia<T> > G; Vertice<T> v;
int nv; Lista<Vertice<T> > adyacentes;
} }

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 35

Implementación

Sevilla
Huelva
Objeto de tipo Adyacencia,
formado por un vértice y una
lista de vértices
Córdoba
Cádiz

Huelva Sevilla

Sevilla Huelva Cádiz Córdoba

Cádiz Huelva

Córdoba

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 36

Implementación

Para un grafo con aristas etiquetadas, cada objeto almacenado en la lista


adyacentes debe contener no sólo el vértice sino también la información asociada
a la arista

template <typename T, typename U> template <typename T, typename U>


class Grafo{ class Adyacencia{
public: public:
// métodos de la interfaz informal // métodos tipo get/set
private: private:
Lista<Adyacencia<T, U> > G; Vertice<T> v;
int nv; Lista<VerticeAdy<T, U> > adyacentes;
} }

template <typename T, typename U>


class VerticeAdy: public Vertice<T>{
public:
// métodos getEtiqueta/setEtiqueta
private:
U etiqueta;
}

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 37

Implementación
Grafo Etiquetado con Pesos

60

Sevilla Objeto de tipo Adyacencia,


Huelva
formado por un vértice y una lista
70 80
de objetos de tipo VerticeAdy
150
75
Córdoba
Cádiz
G

Huelva Sevilla 60

Sevilla Huelva 70 Cádiz 75 Córdoba 80

Huelva
Cádiz
150
Representación mediante
Listas de Adyacencia Córdoba

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 38

Implementación

Listas múltiples de adyacencia

Se utilizan para grafos dirigidos, si lo que queremos saber es, de forma eficiente, los
antecesores de un determinado nodo
Se utiliza otra tabla de listas cuya lista i-ésima contiene los vértices antecesores al vértice i.
Dicha lista se conoce con el nombre de lista de antecesores
Una lista múltiple de adyacencia es una estructura de datos que une ambas tablas en una
única estructura
Hay un nodo por cada arista del grafo
Cada nodo guarda la información de dos vértices (origen y destino de la arista) y dos
punteros
el primero apunta al nodo que almacena la siguiente arista que tiene el mismo vértice destino

el segundo apunta al nodo que almacena la siguiente arista que tiene el mismo vértice origen

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 39

Implementación

template <typename T, typename U> template <typename T, typename U>


class GrafoDirigido{ class nodoGM{
public: public:
typedef NodoGM<T, U>* PtrNodoGM; typedef NodoGM<T, U>* PtrNodoGM;
// métodos de la interfaz informal // métodos tipo get/set
private: private:
PtrNodoGM *adyacentes; Vertice<T> origen, destino;
PtrNodoGM *antecesores; // Para grafos etiquetados:
int nv; // U etiqueta;
Diccionario<T, int> dVertices; PtrNodoGM sigAdy, sigAnt;
} }

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 40

Implementación
A C origen destino

mismo
origen
B (sigAdy)

mismo
destino
D E
(sigAnt)

1 2 3 4 5
DESTINO listas de
antecesores

ORIGEN A B

1
2 B A B C B D

3
4
5 E B E C Las posiciones de ORIGEN y DESTINO son
los valores asociados por el diccionario a
listas de
adyacencia cada vertice (i.e.: <A, 1>, <B, 2>, etc.)

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 41

Implementación

Algoritmo del método adyacentes:

Conjunto<Vertice<T> > adyacentes (const T& v) const


var
Conjunto<Vertice<T> > c
PtrNodoGM p
int pos; Por ejemplo, en el grafo anterior:
fvar
inicio
if dVertices.observar(v, pos) entonces Adyacentes (B) = {A,C,D}
p = adyacentes[pos]
mientras p ≠ NULO hacer
c.poner(p->getDestino())
p = p->getSigAdy()
fmientras
fsi
devolver c
fin

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 42

Implementación
Eficiencia de las operaciones
Operación Matriz Lista Simple(1) Lista Múltiple
n: número de vértices Grafo
m: número de aristas
esVacio
gE: grado máximo de entrada
gS: grado máximo de salida estaVertice

estaArista

vertices

aristas

adyacentes

antecesores

insertarVertice

insertarArista

eliminarVertice
(1) Versión en la que se usa una tabla para
almacenar la información de los vértices
eliminarArista
Estructuras de Datos II Tema 5 Grado en Ingeniería Informática
+
6.4 Recorridos sobre Grafos

43
+ 44

Recorridos sobre grafos

La operación de recorrer un grafo consiste en visitar en un determinado orden todos


aquellos vértices, a partir de un vértice determinado, y que son accesibles desde él

Existen básicamente dos estrategias de recorrido:

en anchura
en profundidad
ambos necesitan una estructura auxiliar para
almacenar los vértices visitados ya que deben
evitarse las visitas reiteradas

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 45

Recorridos sobre grafos


Recorrido en anchura

Consiste en visitar un vértice de partida V, y a continuación, visitar en anchura los vértices


adyacentes que aún no hayan sido procesados

Este método utiliza una cola como estructura auxiliar para mantener los vértices que se
vayan a procesar posteriormente

Los pasos son:

1. Guardar en la cola el vértice de partida y marcarlo como procesado


2. Repetir los pasos 3 y 4 hasta que la cola esté vacía
3. Retirar el nodo frente de la cola (F) y visitar F
4. Guardar en la cola todos los vértices adyacentes al vértice F que no estén procesados, y marcarlos
como procesados

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 46

Recorridos sobre grafos


Algoritmo de recorrido en anchura
void recorrido_anchura (const Grafo& G, const T& v)
var Cola<T> C; Conjunto<Vertice<T> > ady; T x, u, w;
Diccionario<T, bool> procesados; bool vProcesado fvar
Inicio
para todo x en G.vertices() hacer
procesados.asignar(x, falso)
fpara
C.añadir(v)
procesados.asignar(v, verdad)
mientras ¬C.esVacía() hacer
u = C.primero(); C.eliminar(); visitar(u)
ady = G.adyacentes(u)
mientras ¬ady.esVacío() hacer
w = ady.quitar().getObj();
procesados.observar(w, vProcesado )
si ¬vProcesado entonces
C.añadir(w)
procesados.asignar(w, verdad)
fsi
fmientras
fmientras
fin
Estructuras de Datos II Tema 5 Grado en Ingeniería Informática
+ 47

Recorridos sobre grafos


Ejemplo

Vértices recorridos desde D Estado de la cola


Frente D Final
B

A
D
H

T C
R

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 48

Recorridos sobre grafos


Recorrido en profundidad

Consiste en visitar el vértice de partida V, y a continuación visitar en profundidad los


vértices adyacentes que aún no hayan sido procesados

Generalización del recorrido en preorden de los árboles

Este método utiliza una pila como estructura auxiliar para mantener los vértices que se
vayan a procesar posteriormente

Los pasos son:

1. Guardar en la pila el vértice de partida y marcarlo como procesado


2. Repetir los pasos 3 y 4 hasta que la pila esté vacía
3. Desapilar el nodo que está en la cima (C), y visitar C
4. Guardar en la pila todos los vértices adyacentes al vértice C que no estén procesados, y marcarlos
como procesados

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 49

Recorridos sobre grafos


Algoritmo de recorrido en profundidad
void recorrido_profundidad (const Grafo& G, const T& v)
var Pila<T> P; Conjunto<Vertice<T> > ady; T x, u, w;
Diccionario<T, bool> procesados; bool vProcesado fvar
inicio
para todo x en G.vertices() hacer
procesados.asignar(x, falso)
fpara
P.apilar(v)
procesados.asignar(v, verdad)
mientras ¬P.esVacía() hacer
u = P.cima(); P.desapilar(); visitar(u)
ady = G.adyacentes(u)
mientras ¬ady.esVacío( ) hacer
w = ady.quitar( ).getObj()
procesados.observar(w, vProcesado)
si ¬vProcesado entonces
P.apilar(w)
procesados.asignar(w, verdad)
fsi
fmientras
fmientras
fin
Estructuras de Datos II Tema 5 Grado en Ingeniería Informática
+ 50

Recorridos sobre grafos


Ejemplo

Vértices recorridos desde D Estado de la pila

Fondo D Cima
B

A
D
H

T C
R

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+
6.5 Ordenación Topológica

51
+ 52

Ordenación topológica

¿Qué es?: Es la ordenación lineal de todos los nodos de un Grafo según la distribución
y sentido de sus aristas

La ordenación topológica es aplicable sólo a Grafos Dirigidos Acíclicos

Aplicación:
Representación de las fases de un proyecto mediante un grafo dirigido acíclico:
Los vértices representan tareas y las aristas relaciones temporales entre ellas
Ordenación topológica: Lista de tareas en un orden lógico que puedan realizarse

Un vértice sólo se visita si han sido visitados todos sus predecesores


En particular, al comenzar sólo se pueden visitar los nodos que no tienen ningún predecesor

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 53

Ordenación topológica

Lista<Vertice<T> > ordTopologica (const Grafo& G)


var Lista<Vertice<T> > L; T v;
Conjunto<Vertice<T> > vert
fvar
inicio
vert= G.Vertices()
para todo v en vert hacer
escoger v ∈ L tal que todos sus predecesores estén en L
L.añadirDer(v)
fpara
devuelve L
fin

Problema: es costoso buscar vértices sin predecesores repetidas veces


Una solución: calcular previamente cuántos predecesores tiene cada
vértice, almacenar el resultado en un vector y actualizarlo siempre que se
incorpore un nuevo vértice a la solución

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 54

Ordenación topológica
Lista<Vertice<T> > ordTopologica (const Grafo& G)
var Lista<Vertice<T> > L; T v, w;
Conjunto<T> ceroPred
Conjunto<Vertice<T> > vert, suc
Diccionario <T, int> numPred
int np;
fvar
inicio
vert = G.vertices()
para todo v en vert hacer
numPred.asignar(v.getObj(), 0); ceroPred.poner(v)
fpara
para todo v en vert hacer
suc = G.adyacentes(v)
para todo w en suc hacer
numPred.observar(w, np)
numPred.asignar(w, np + 1)
ceroPred.eliminar(w)
fpara
fpara

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 55

Ordenación topológica

mientras ceroPred ≠ ∅ hacer


v = ceroPred.quitar()
L.añadirDer(Vertice<T>(v))
suc = G.adyacentes(v)
para todo w en suc hacer
numPred.observar(w, np)
np = np - 1
numPred.asignar(w, np)
si np = 0 entonces
ceroPred.poner(w)
fsi
fpara
fmientras
devuelve L
fin

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 56

Ordenación topológica

Ejemplo: ordTopologica(G)

A
D
H L

T C
R

A B C D H R T

numPred

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


6.6 Caminos mínimos sobre
+
Grafos: Algoritmos de Dijkstra
y Floyd

57
+ 58

Caminos mínimos sobre grafos

Uno de los problemas que se plantea con frecuencia en los grafos es determinar el
camino de menor coste entre un par de vértices. Este problema se plantea,
generalmente, para grafos dirigidos y valorados (con factor de peso en las aristas)

Suponemos que cada arista tiene asociada un coste cij no negativo, es decir,

∀(vi , vj), cij ≥ 0

El coste de un camino C = v1, v2, ..., vk es la suma de los costes de las aristas de C
k −1
coste(C ) = ∑ c i , i +1
i =1

El objetivo es encontrar el camino desde v1 a vk con coste mínimo

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 59

Caminos mínimos sobre grafos: Dijkstra

Este algoritmo resuelve el problema de encontrar el camino de coste mínimo desde


un vértice v al resto de los vértices del grafo. Puede usarse, por ejemplo, para
decidir si un punto de una ciudad es adecuado para ubicar un determinado servicio
(hospital, policía, bomberos, etc.) de forma que se pueda alcanzar rápidamente
cualquier punto del área a servir

El algoritmo tiene como precondición que el grafo G=(V, A) tiene que ser dirigido,
valorado y con factores de peso positivos

Es un algoritmo voraz, es decir, en cada etapa se selecciona la mejor solución


entre las disponibles

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 60

Caminos mínimos sobre grafos: Dijkstra

El algoritmo comienza inicializando el conjunto de vértices por visitar con todos los
vértices del grafo y termina cuando no quedan vértices por visitar. En cada
iteración, escoge el vértice por visitar con menor coste desde v usando un camino
que pase sólo por vértices ya incluidos en el conjunto de vértices tratados

Para mantener, en cada paso, la distancia mínima desde el origen podemos usar
una tabla llamada D (distancia), que mantiene la distancia desde el origen a cada
vértice del grafo

En cada iteración, una vez seleccionado de entre los vértices por visitar el vértice w
más próximo a v, se actualiza la tabla D para aquellos vértices u (no visitados)
adyacentes de w y que cumplen que D[w] + coste(G, w, u) < D [u]

Para indicar que no existe camino desde el vértice v1 hasta el vértice v2 usaremos
la indeterminación ∞

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 61

Caminos mínimos sobre grafos: Dijkstra


int* Dijkstra (const Grafo& G, Vértice v, int* A)
var Conjunto <Vértice> T; int* D; Vértice u, w; int costeMin, n fvar • El vector D almacena las distancias mínimas
inicio • El vector A almacena el antecesor inmediato en el
n = G.vertices().cardinalidad(); D = nuevo int [n]; A = nuevo int [n] camino mínimo
para todo w en G.vertices() hacer • T es el conjunto de vértices que quedan por visitar
D[w] = ∞; A[w] = 0
T.poner(w)
fpara
D[v] = 0
repetir n-1 veces se inicializa D a un valor máximo. En A ponemos el valor 0 para indicar
costeMin = ∞ que aún no hay antecesores en el camino mínimo
para todo u en T hacer
si D[u] ≤ costeMin entonces se elige de T el vértice con menor distancia a
w=u v, y se almacena en w
costeMin = D[u]
fsi
fpara se marca w como vértice tratado
T.quitar(w)
para todo u en T hacer
se recalculan las nuevas distancias mínimas de los adyacentes de w y
si D[w] + coste (G,w,u) < D[u] entonces se almacena en A el antecesor inmediato, es decir, el vértice con el que
D[u] = D[w] + coste (G,w,u) se forma el camino más corto
A[u] = w
fsi
fpara
frepetir
devuelve D
fin

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 62

Caminos mínimos sobre grafos: Dijkstra


v1 v2 v1 v2 v1 v2
5 5 5
0 ∞ 0 5 0 4
2 2 2 2 2 2
v3 v3 v3

3 ∞ 1 2
3 2 1 2
3 2 1 2
2 5 2 5 2 5

∞ ∞ 3 ∞ 3 7
4 v4 4 v4 4 v4
v5 v5 v5
v1 v2 v1 5 v2 v1 v2
5 5
0 4 0 4 0 4
2 2 2 2 2 2
v3 v3 v3

2 1 2 2 1 2 2 1 2
3 3 3
2 5 2 5 2 5

3 7 3 5 3 5
v5 4 v4 4 v4 v5 4 v4
v5

Iteración Tratados T D[2] D[3] D[4] D[5]


Inicial v1 v2 v3 v4 v5 ∞ ∞ ∞ ∞

1
2
3
4

A[1] = A[2] = A[3] = A[4] = A[5] =

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 63

Caminos mínimos sobre grafos: Floyd

En algunas aplicaciones puede resultar interesante determinar el camino mínimo


entre todos los pares de vértices de un grafo dirigido valorado. El problema podría
resolverse aplicando reiteradamente el algoritmo de Dijkstra. Otra posibilidad es
utilizar un método más directo conocido como el algoritmo de Floyd

Este algoritmo se basa en el esquema de programación dinámica. Este método


utiliza una tabla para ir almacenando los resultados correspondientes a instancias
más sencillas del problema a resolver

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 64

Caminos mínimos sobre grafos: Floyd

Sea G un grafo dirigido y valorado. Suponemos que los vértices están numerados de 1 a n.
En este caso, utilizamos una matriz con los pesos de las aristas, de tal forma que cada
elemento de la matriz representa el peso cij asociado a la arista (vi , vj). Utilizaremos cij = ∞
para representar que no existe arista entre los vértices vi y vj

La idea principal consiste en encontrar una matriz D de n x n elementos, de tal forma que
cada elemento Dij sea el coste mínimo de los caminos entre vi y vj

Para calcular el camino de coste mínimo entre los vértices i y j podemos considerar dos
posibilidades: no pasar por el vértice k, en cuyo caso tendremos que calcular el mejor
camino con el resto de los vértices (Dk-1[i, j]), o bien pasar por el vértice k, en cuyo caso
tendremos que obtener caminos que vayan de i a k y de k a j

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 65

Caminos mínimos sobre grafos: Floyd

El algoritmo comienza con una inicialización natural de D (D0) y se genera iterativamente la


secuencia de matrices D1, D2, ..., Dn, cuyos elementos tienen el siguiente significado:
D0[i, j] = cij, peso asociado a la arista desde el vértice i al vértice j

D1[i, j] = min (D0[i, j], D0[i, 1] + D0[1, j]). Menor de los costes entre el anterior camino desde i hasta j y
la suma de los costes de caminos desde i hasta 1 y 1 hasta j

D2[i, j] = min (D1[i, j], D1[i, 2] + D1[2, j]). Menor de los costes entre el anterior camino desde i hasta j y
la suma de los costes de caminos desde i hasta 2 y 2 hasta j

Dn[i, j] = min (Dn-1[i, j], Dn-1[i, n] + Dn-1[n, j]). Menor de los costes entre el anterior camino desde i
hasta j y la suma de los costes de caminos desde i hasta n y n hasta j

Como en el algoritmo de Dijkstra, para cada vértice sería conveniente almacenar el índice
del último vértice que ha conseguido que el camino sea mínimo desde vi hasta vj. Para ello,
se utiliza una matriz de vértices según el siguiente criterio:
• A(vi, vj) = 0, si no hay camino de vi a vj ,o hay una arista entre los vértices
• A(vi, vj) = vk, si vj es accesible desde vi a través de vk en el camino mínimo entre ambos vértices
Estructuras de Datos II Tema 5 Grado en Ingeniería Informática
+ 66

Caminos mínimos sobre grafos: Floyd


constante n = G.vertices().cardinalidad() Implementación del algoritmo de Floyd
tipo int [n][n] MatrizDistancias
int [n][n] MatrizCamino

MatrizDistancias Floyd (const Grafo& G, MatrizCamino A)


var int i, j, k; MatrizDistancias D fvar
inicio
inicializarCostes (G, D) { almacena en D0 los pesos asociados a las aristas de G.
para i = 1 hasta n hacer El camino mínimo de un vértice a sí mismo se considera 0 }
para j = 1 hasta n hacer
A[i, j] = 0
fpara
fpara
para k =1 hasta n hacer { el índice k indica el subíndice de la matriz D que se está generando}
para i = 1 hasta n hacer { el índice i indica la fila }
para j =1 hasta n hacer { el índice j indica la columna }
si (D[i,k] + D[k,j] < D[i,j]) entonces
D[i,j] = D[i,k] + D[k,j]; A[i,j] = k
fsi
fpara
fpara
fpara
devuelve D
fin

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 67

Caminos mínimos sobre grafos: Floyd


Camino mínimo entre cada par de vértices
void imprimirCaminos (MatrizDistancias D, MatrizCamino A)
var int i, j fvar
inicio
para i = 1 hasta n hacer
para j = 1 hasta n hacer
si D[i, j] < ∞ entonces
escribir (“camino de “, i, “ a “, j, “: “)
escribir (i); imprimirCamino (i, j, A); escribir (j)
fsi
fpara
fpara
fin

void imprimirCamino (int i, int j, MatrizCamino A)


var int k fvar
inicio
k = A[i,j]
si k > 0 entonces { hay un camino no directo }
imprimirCamino (i, k, A)
escribir (k)
imprimirCamino (k, j, A)
fsi
fin

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


6.7 Árbol de expansión de coste
+
mínimo: Algoritmos de Prim y
Kruskal

68
+ 69

Árbol de expansión de coste mínimo

Un problema típico relacionado con los grafos consiste en diseñar una red de
transporte (carreteras, ferrocarril, ...) que conecte una serie de poblaciones
construyendo el menor número posible de kilómetros de vía

Se trata de encontrar un subgrafo que contenga todos los vértices, que siga siendo
conexo y que el coste total de sus aristas sea mínimo

Se aplica en grafos no dirigidos, conexos y etiquetados (etiquetas no negativas)

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 70

Árbol de expansión de coste mínimo


Árbol libre:
Grafo no dirigido, conexo y acíclico
Diferencias con los árboles generales: no tienen raíz y los hijos no están
ordenados
Todo árbol libre con n vértices tiene n – 1 aristas
Si se borra una arista deja de ser conexo, y si se añade una nueva arista,
entonces aparecen ciclos

Árbol de extensión o de recubrimiento (spanning tree) de coste mínimo:


Subconjunto G’ del grafo G
Contiene todos los vértices de G
Es conexo y acíclico
No existe otro G’’ tal que la suma de los costes de las aristas de G’’ sea
menor que la suma de los costes de las aristas de G’

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 71

Árbol de expansión de coste mínimo


Ejemplos: Grafo Árbol Libre

v1 v1
6 5
1 1
v2 5 5 v4 v2 5 v4
v3 v3

3 6 4 3 4
2 2

6
v5 v6 v5 v6

2 v2 2 2 v2 2

3 v4 v4
v1 1 v1 1
4
v3 v3

5
3 6 3

4 4
v5 v6 v5 v6

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 72

Árbol de expansión de coste mínimo

Estrategia: algoritmos voraces

Prim: parte de un vértice cualquiera y va extendiendo el árbol de


recubrimiento, incorporando un nuevo vértice en cada iteración, hasta cubrir
todos los vértices del grafo

Kruskal: parte de un bosque de árboles formados por un único vértice cada


uno, de forma que en cada iteración se conectan un par de árboles mediante
una arista, hasta que en el bosque quede un único árbol

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 73

Árbol de expansión de coste mínimo:


Algoritmo de Prim

Dado el grafo G = (V, A), se mantienen dos particiones:


U: vértices que se encuentran enlazados por las aristas del árbol que se está
construyendo
V – U: resto de vértices

En cada iteración se incrementa el subconjunto U con un nuevo vértice, hasta que U = V


Inicialmente, U contiene un único vértice, que puede ser cualquiera
En cada paso, se localiza la arista (u, v) de menor coste tal que u ∈ U y v ∈ V – U

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 74

Árbol de expansión de coste mínimo:


Algoritmo de Prim
Conjunto<Arista> Prim (const Grafo& G)
var
Conjunto <Vertice> U, V
Conjunto <Arista> T
Vertice u, v
fvar
inicio
U.poner(G.vertices( ).quitar( ))
V = G.vertices( )
mientras U ≠ V hacer
escoger la arista (u, v) de coste mínimo tal que u ∈ U y v ∈ V – U
T.poner(Arista(u, v))
U.poner(v)
fmientras
devuelve T
fin

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 75

Árbol de expansión de coste mínimo:


Algoritmo de Prim
Ejemplo

10 14
v1 v4 v6

9
3 7
7 v7 v8
v3
5 4 2
6
v2 v5
5

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 76

Árbol de expansión de coste mínimo:


Algoritmo de Kruskal
Dado un grafo G = (V, A), se comienza con un grafo T = {V, ∅} ⇒ T es un grafo con todos los
nodos de G pero sin ninguna arista

Durante el algoritmo se mantendrá siempre un bosque de árboles. Inicialmente, el bosque


contiene tantos árboles como vértices tiene el grafo ⇒ cada uno de los árboles está formado
por un único vértice

Para añadir las aristas examinamos el conjunto A en orden creciente según su coste:

Si la arista seleccionada conecta dos vértices de árboles distintos, se añade al conjunto


de aristas de T puesto que formará parte del árbol de expansión y ambas componentes
se unen en una única componente conexa

Si la arista conecta dos vértices que pertenecen al mismo árbol, no se añade para evitar
la aparición de ciclos en el árbol de expansión

Cuando todos los vértices de T estén en una única componente conexa, habremos encontrado
el árbol de expansión buscado

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ Árbol de expansión de coste mínimo: 77

Algoritmo de Kruskal
Conjunto<Arista> Kruskal (const Grafo& G)
var
Lista <Arista> A; Conjunto <Arista> T; Arista ai; V w;
Diccionario<V, int> B; int i, n; int arbol1, arbol2;
fvar
inicio
n = G.vertices().cardinalidad()
A = secuencia de las aristas de G ordenada crecientemente según su coste
i=0
para todo w en G.vertices() hacer
B.asignar(w, i); i++
fpara
i=1
mientras T.cardinalidad( ) < n - 1 hacer
ai = A.observa[i]
B.observar(ai.getOrigen( ), arbol1); B.observar(ai.getDestino( ), arbol2);
si arbol1 ≠ arbol2 entonces
T.poner(ai)
fusionar (B, ai.getOrigen( ), ai.getDestino( ))
fsi
i++
fmientras
devuelve T
fin

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática


+ 78

Árbol de expansión de coste mínimo:


Algoritmo de Kruskal
Ejemplo

10 14
v1 v4 v6

9
3 7
7
v7 v8
v3
4
5 2
6
v2 v5
5

v1 v2 v3 v4 v5 v6 v7 v8

B 1 21 312 4
2
1 5 4
6
2
1 17

Estructuras de Datos II Tema 5 Grado en Ingeniería Informática

También podría gustarte