Sie sind auf Seite 1von 17

1.

INTRODUCCIÓN
El origen de la teoría de grafos suele situarse en el problema de “los siete puentes de Könisberg”, propuesto por el
matemático alemán Euler.
“¿Se puede trazar un paseo circular que pasa por los 7 puentes sin repetir ninguno?”
Realmente, la forma y dimensión de las islas y las orillas no tienen importancia, el problema se puede formular
utilizando el siguiente gráfico, donde los vértices son las islas y orillas del río y las aristas los puentes.

A D

Los grafos son modelos matemáticos que reproducen numerosas situaciones reales como: una red de
carreteras, la red de enlaces ferroviarios o aéreos, la red eléctrica de una ciudad, circuitos electrónicos,
diagramas de flujo de los algoritmos, redes de ordenadores, etc.

Gráficamente el grafo se representa como un conjunto de puntos (vértices o nodos) unidos a través de líneas
(arcos o aristas). Así por ejemplo las diferentes estructuras de flujo de un lenguaje de programación de alto
nivel podrían representarse de la siguiente manera:

V F V V

. . .
F
F

Secuencia If-then-else While Do-While Switch


Los grafos son estructuras de datos no lineales que tienen una naturaleza generalmente dinámica. Definimos
un grafo como un conjunto finito de vértices unidos por un conjunto de arcos. Luego, el grafo G se
representa por G = (V, E), donde V es el conjunto de vértices o nodos y E el conjunto de arcos o aristas.

Como cada uno de los arcos del conjunto E es un par de vértices del conjunto V de la forma [v1, v2] donde v1,
v2 ∈ V. Si este par se considera ordenado, entonces hablaremos de GRAFOS DIRIGIDOS o DIGRAFOS . En
otro caso, si el par NO se considera ordenado, estaremos hablando de GRAFOS NO DIRIGIDOS.

Si el grafo es No dirigido se cumple que si [v1, v2] ∈ E, entonces implica que [v2, v1] ∈ E también. Esta
condición no tiene por qué cumplirse en grafos dirigidos.

Un ejemplo de grafo dirigido lo constituye la red de aguas de una ciudad, donde el agua circula por cada
tubería en un único sentido. Por el contrario, la red de carreteras de un país representa en general un grafo
no dirigido, puesto que una misma carretera puede ser recorrida en ambos sentidos.

A B A B

C D C D
Grafo no dirigido Grafo dirigido

Para ambos grafos, el conjunto correspondiente a los vértices sería: V(G) = {A, B, C, D}
Sin embargo, el conjunto de arcos para cada grafo es distinto, así:
E(G no dirigido) = { [A,B], [B,A], [B,D], [D,B], [A,D], [D,A], [C,D], [D,C]}

E(G dirigido) { [A,B], [B,A], [A,D], [D,B], [D,C]}

DEFINICIONES BÁSICAS

A continuación vamos a definir una serie de conceptos importantes sobre esta estructura de datos.

 Grafo Completo Un grafo se dice completo si pertenecen al conjunto de arcos E todos los arcos
posibles. El número de arcos será:
 Grafos no dirigidos: n (n – 1) / 2
 Grafos dirigidos: n (n – 1)

 Adyacencia e Incidencia
 Grafos no dirigidos: Si existe un arco [v1, v2] ∈ E(G), se dice que los vértices v1 y v2 son
adyacentes, además se dice que dicho arco es incidente en los vértices v1 y v2.

 Grafos dirigidos: Si existe un arco [v1, v2] ∈ E(G), se dice que el vértice v2 es adyacente al
vértice v1, además se dice que dicho arco es incidente en el vértice v2.

 Subgrafo Se dice que G1 es un subgrafo del grafo G si se cumple que:


V(G1) ⊆ V(G)
E(G1) ⊆ E(G)
 Camino Un camino desde el vértice vi al vértice vj en un grafo G es ua secuencia de vértices: vi, vk1,
vk2, . . ., vkn-1, vkn, vj, tal que [vi, vk1], [vk1, vk2], . . . , [vkn-1, vkn], [vkn, vj] ∈ E(G).

 Longitud de un camino Es el número de arcos que forman el camino.

 Camino simple Es un camino en el que todos sus arcos son distintos.

 Camino elemental Es un camino en el que no se utiliza un mismo vértice dos veces. Luego, todo camino
elemental es simple.

 Camino Euleriano Es un camino simple y además está formado por todos los arcos del grafo.

 Circuito o Ciclo Es un camino en el que coinciden los vértices inicial y final.

 Circuito o Ciclo simple Es un camino simple en el que coinciden los vértices inicial y final.

 Circuito o Ciclo elemental Es un camino elemental en el que coinciden los vértices inicial y final.

 Grafo simple Es un grafo en el que no existen bucles y no existe más que un camino para unir dos nodos.

 Conexión entre vértices Se dice que vi y vj están conectados si existe un camino en el grafo G desde vi
hasta vj. Cuando el grafo no es dirigido, también existirá un camino desde vj hasta vi.

 Grafo conexo Un grafo NO dirigido se dice que es conexo si para cada par de vértices V i y Vj distintos,
existe un camino en G. Es decir, si cada par de vértices están conectados.

 Grafo fuertemente conexo Un grafo dirigido se dice que es fuertemente conexo si para cada par de
vértices Vi y Vj distintos, existe un camino en G. Es decir, si cada par de vértices están conectados.

 Grado de un vértice En grafos no dirigidos es el número de arcos que tienen dicho vértice como
extremo.

 Grado de entrada de un vértice En grafos dirigidos es el número de arcos que llegan o inciden en
dicho vértice.

 Grado de salida de un vértice En grafos dirigidos es el número de arcos que salen de dicho vértice.

 Grafo valorado o ponderado Es aquel grafo donde asociado con cada arco existe un valor que es el
coste del arco.

 Coste de un camino Es la suma de los costes de los arcos asociados a los arcos que forman el camino.

 Grafo no dirigido bipartido Se dice que es bipartido cuando el conjunto de sus vértices puede ser
divido en dos subconjuntos (disjuntos) de tal forma que cualquiera de los arcos que forman el grafo tiene
cada uno de sus extremos en un subconjunto distinto.
ESPECIFICACIÓN FORMAL DEL TAD GRAFO

Las operaciones necesarias para el manejo de un Grafo son entre otras las siguientes:

GRAFOVACIO: Crea un grafo vacío


AÑADIRVERTICE: Añade un vértice al conjunto de vértices de un grafo.
AÑADIRARCO: Añade un arco al conjunto de arcos de un grafo.
CONJVERTICE: Devuelve el conjunto de vértices de un grafo.
CONJARCOS: Devuelve el conjunto de arcos de un grafo.
CONJADYACENTES: Devuelve el conjunto de vértices adyacentes a otro vértice dado en un grafo.
BORRARVERTICE: Elimina un vértice del conjunto de vértices de un grafo y todos los arcos
asociados a dicho vértice del conjunto de arcos del grafo.
BORRARARCO: Elimina un arco del conjunto de arcos de un grafo.
PERTENECEARCO: Decide si un arco está en el conjunto de arcos del grafo.
PERTENECEVERTICE: Decide si un vértice está en el conjunto de vértices del grafo.

Si se trabaja con grafos valorados o ponderados, se debe definir una función COSTE que devuelva el valor
asociado a cada arco del grafo.

La especificación formal para el TAD Grafo es la siguiente:

Tipo: Tgrafo, Tvertice, Tarco


Valores: Estructura compuesta por un conjunto de vértices y un conjunto de arcos.

Sintaxis:
* GrafoVacio  Tgrafo
* AñadirVertice (Tgrafo, Tvertice)  Tgrafo
* AñadirArco (Tgrafo, Tarco)  Tgrafo
ConjVertices (Tgrafo)  Tconjunto (Tvertice)
ConjArcos (Tgrafo)  Tconjunto (Tarcos)
ConjAdyacentes (Tgrafo, Tvertice)  Tconjunto (Tvertice)
BorrarVertice (Tgrafo, Tvertice)  Tgrafo
BorrarArco (Tgrafo, Tarco)  Tgrafo
PerteneceVertice (Tgrafo, Tvertice)  Boolean
PerteneceArco (Tgrafo, Tarco)  Boolean

Semántica: ∀G ∈ Tgrafo, ∀ v1,v2,v3,v4 ∈ Tvertice


ConjVertices (GrafoVacio)  ConjuntoVacio
ConjVertices (AñadirVertice (G, v1))  InsertarConjunto (ConjVertices (G), v1)
ConjVertices (AñadirArco (G, [v1, v2])) 
InsertarConjunto ( InsertarConjunto (ConjVertices (G), v1), v2)

ConjArcos (GrafoVacio)  ConjuntoVacio


ConjArcos (AñadirVertice (G, v1))  ConjArcos (G)
ConjArcos (AñadirArcos (G, [v1,v2]))  InsertarConjunto (ConjArcos (G), [v1,v2])

ConjAdyacentes (GrafoVacio, v1)  ConjuntoVacio


ConjAdyacentes (AñadirVertice (G, v1), v2)  ConjAdyacentes (G, v2)
ConjAdyacentes (AñadirArco (G, [v1,v2]), v3) 
• si v1 = v3  InsertarConjunto (ConjAdyacentes (G, v3), v2)
• si v2 = v3  InsertarConjunto (ConjAdyacentes (G, v3), v1)
• en otro caso ConjAdyacentes (G, v3)

BorrarVertice (GrafoVacio, v1)  GrafoVacio


BorrarVertice (AñadirVertice (G, v1), v2) 
• si v1 = v2  BorrarVertice (G, v2)
• en otro caso AñadirVertice ( BorrarVertice (G, v2), v1)
BorrarVertice (AñadirArco (G, [v1,v2]), v3) 
• si v1 = v3  AñadirVertice ( BorrarVertice(G,v3), v2)
• si v2 = v3  AñadirVertice (BorrarVertice (G, v3), v1)
• en otro caso  AñadirArco (BorrarVertice (G, v3), [v1,v2])

BorrarArco (GrafoVacio, [v1,v2])  GrafoVacio


BorrarArco (AñadirVertice (G, v1), [v2, v3])  AñadirVertice (BorrarArco ( G, [v2,v3]), v1)
BorrarArco (AñadirArco (G, [v1,v2]), [v3,v4]) 
• si [v1,v2] = [v3,v4]  BorrarArco (G, [v3,v4])
• en otro caso  AñadirArco (BorrarArco (G, [v3,v4]), [v1,v2])

PerteneceArco (GrafoVacio, [v1,v2])  falso


PerteneceArco (AñadirVertice (G, v1), [v2,v3])  PerteneceArco (G, [v1,v2])
PerteneceArco (AñadirArco (G, [v1,v2]), [v3,v4]) 
• si [v1,v2] = [v3,v4]  verdadero
• en otro caso  PerteneceArco (G, [v1,v2])

PerteneceVertice (GrafoVacio, v1)  falso


PerteneceVertice (AñadirVertice (G, v1), v2) 
• si v1 = v2  verdadero
• en otro caso  PerteneceVertice (G, v1)
PerteneceVertice (AñadirArco( G, [v1,v2]), v3) 
• si v1 = v3 o v2 = v3  verdadero
• en otro caso  PerteneceVertice (G, v3)

Esta especificación y sus posteriores realizaciones están basadas en el TAD Conjunto


Los constructores son: GrafoVacio, AñadirVertice yAñadirArco.

IMPLEMENTACIÓN DE GRAFOS INDEPENDIENTEMENTE DEL TIPO CONJUNTO

Vamos a definir con más detalle las operaciones de manejo de la estructura Grafo definida conceptualmente,
para lo cual se supone la existencia del conjunto de vértices y del conjunto de arcos del grafo G. La
realización de estos dos conjuntos, así como las operaciones para su propio manejo no deben influir en la
realización de las operaciones de TAD Grafo.

La definición de tipos será:

typedef . . . Tvertice;

typedef struct
{ Tvertice origen, destino;
} Tarco;

typedef . . . TConjVertices;

typedef . . . TConjArcos;

typedef struct
{ TConjVertices vertice;
TConjArcos arco;
} Tgrafo;

Según esta definición de tipos, se comprueba que un grafo es un conjunto de vértices y un conjunto de arcos.

• Tgrafo GrafoVacio ( void);


// PRE: Ninguna
// POST: Crea un grafo vacío, poniendo los conjuntos de vértices y de arcos vacío.

Tgrafo GrafoVacio (void)


{ Tgrafo G;

ConjVerticesVacio(&G.vertice);
ConjArcosVacio(&G.arcos);

return(G);
}

• Tgrafo AgnadirVertice ( Tgrafo G, Tvertice v);


// PRE: Ninguna
// POST: Añade el vértice v al conjunto de vértices del grafo G.

Tgrafo AgnadirVertice ( Tgrafo G, Tvertice v)


{ InsertarConjVertices(&G.vértice, v);

return(G);
}

• Tgrafo AgnadirArco ( Tgrafo G, Tarco a);


// PRE: Ninguna
// POST: Añade el arco a al conjunto de arcos del grafo G.

Tgrafo AgnadirArco ( Tgrafo G, Tarco a)


{ InsertarConjArcos(&G.arcos, a);
InsertarConjVertices(&G.vertice, a.origen);
InsertarConjVertices(&G.vertice, a.destino);

return(G);
}
• void ConjVertices ( Tgrafo G, TConjVertices *Vertices) ;
// PRE: Ninguna
// POST: Devuelve el conjunto de vértices del grafo G.

void ConjVertices ( Tgrafo G, TConjVertices *Vertices)


{
AsignarConjVertices(G.vertices, Vertices);
}

• void ConjArcos ( Tgrafo G, TConjArcos *Arcos);


// PRE: Ninguna.
// POST: Devuelve el conjunto de arcos del grafo G.

void ConjArcos ( Tgrafo G, TConjArcos *Arcos)


{
AsignarConjArcos(G.arcos, Arcos);
}

• void ConjAdyacentes ( Tgrafo G, Tvertice v, TconjVertices *C);


// PRE: Ninguna
// POST: Devuelve el conjunto de vértices adyacentes a otro vértice dado en el grafo G.

void ConjAdyacentes ( Tgrafo G, Tvertice v, TconjVertices *C)


{ Tarco a;

ConjVerticesVacio(C);
a.origen = v;

while (! EsConjVerticesVacio (G.vertices))


{ a.destino = ElegirConjVertices(&G.vértices);
if (PerteneceConjArcos(G.arcos, a))
InsertarConjVertices (&C, a.destino);
}
}

• Tgrafo BorrarArco ( Tgrafo G, Tarco a);


// PRE: Ninguna.
// POST: Elimina el arco a del conjunto de arcos del grafo G.

Tgrafo BorrarArco ( Tgrafo G, Tarco a)


{ BorrarConjArcos(&G.arcos, a);

return (G);
}

• Tgrafo BorrarVertice ( Tgrafo G, Tvertice v);


// PRE: Ninguna.
// POST: Elimina el vértice v del conjunto de vértices del grafo G.

Tgrafo BorrarVertice ( Tgrafo G, Tvertice v)


{ TconjArcos CA1, CA2;
Tarco a;

ConjArcosVacio(&CA1);
ConjArcosVacio(&CA2);

BorrarConjVertices(&G.vertices, v);
AsignarConjArcos(G.arcos, &CA1);

while (!EsConjArcosVacio (CA1))


{ a = ElegirConjArcos(&CA1);
if ((a.origen != v) && (a.destino != v))
InsertarConjArcos (&CA2, a);
}

AsignarConjArcos(CA2, &G.arcos);

return (G);
}

• int PerteneceArco ( Tgrafo G, Tarco a);


// PRE: Ninguna.
// POST: Decide si arco a está en el conjunto de arcos del grafo G.

int PerteneceArco ( Tgrafo G, Tarco a)


{
return (PerteneceConjArcos(G.arcos, a));
}

• int PerteneceVertice ( Tgrafo G, Tvertice v);


// PRE: Ninguna.
// POST: Decide si un vértice v está en el conjunto de vertices del grafo G.

int PerteneceVertice ( Tgrafo G, Tvertice v)


{
return (PerteneceConjVertices(G.vertices, v));
}
REALIZACIONES DEL TAD GRAFOS DEPENDIENDO DEL CONJUNTO DE ARCOS

En el apartado anterior se supuso la existencia de los tipos de datos TConjVertices y TConjArcos, así como
las operaciones necesarias para el manejo de los mismos. Para el caso de TConjVertices se puede utilizar
varios tipos de realizaciones como pueden ser:
1. un array lógico, donde cada posición del vector indica con un 1 ó 0 la existencia o ausencia del
elemento en el conjunto.
2. un array de los elementos del conjunto uno detrás de otro.
3. una lista simplemente enlazada de los elementos que forman parte del conjunto.
Sin embargo, el TConjArcos se trata de un conjunto de pares de vértices y la forma de implementar dicho
conjunto, determina la representación del grafo.

IMPLEMENTACIÓN DE GRAFOS MEDIANTE MATRICES DE ADYACENCIA


Sea el grafo G = (V, E) donde V = { V0, V1, V2, . . ., Vn-1}. La matriz de adyacencia de G será una matriz A de
n x n elementos, cada uno de los cuales toma valores lógicos:

1 si [Vi, Vj] ∈ E
A(i,j) =
0 en caso contrario

Sea el siguiente grafo y su correspondiente matriz de adyacencia:

0 1 2 0 1 2 3 4 5
0 0 1 0 0 1 0
1 1 0 1 0 1 0
2 0 1 0 0 1 0
3 4 5
3 0 0 0 0 1 0
4 1 1 1 1 0 1
5 0 0 0 0 1 0

Cuando el grafo sea no dirigido, la matriz de adyacencia será simétrica. Esta afirmación no se puede hacer
cuando el grafo es dirigido.

Para utilizar esta realización es necesario establecer una biyección entre el conjunto de vértices y un
subrango de los enteros que se corresponden con el tamaño de la matriz y que serán los índices de la misma.
Es decir, si el conjunto de vértices del grafo es {V0, V1, . . ., Vn-1} se establecerá la biyección entre este
conjunto y el subrango desde 0 hasta (n-1) y la dimensión de la matriz será de n x n.
Los arcos del grafo estarán representados por los distintos valores de la matriz. Así, la existencia del arco
[vi, vj] vendrá dada por el valor de A[i, j].

La definición de tipos será:

#define MaxNodos . . . // Nº de vértices del grafo.


typedef int Tvertice; // Información asociada a cada vértice.

typedef struct
{ Tvertice origen, destino;
} Tarco;

typedef struct
{ int MVertices [MaxNodos];
int MArcos [MaxNodos][MaxNodos];
} Tgrafo;

Según estas declaraciones de tipos de datos, el conjunto de vértices se ha representado mediante un


vector de valores lógicos y el conjunto de arcos mediante una matriz de adyacencia. La realización de las
operaciones sobre el conjunto de arcos son las siguientes.

void ConjArcosVacio (int Arcos [][MaxNodos])


{ int i,j;
for (i = 0; i < MaxNodos; i++)
for (j = 0; j < MaxNodos; j++)
Arcos [i, j] = 0;
}

int EsConjArcosVacio (int Arcos [][MaxNodos])


{ int i = j = hayarcos = 0;

do
{ do
{ hayarcos = Arcos[i, j] ;
j++;
} while ((!hayarcos) && (j < MaxNodos));
i++;
j = 0;
} while ((!hayarcos) && (i < MaxNodos));

return (!hayarcos);
}

int PerteneceConjArcos (int Arcos [][MaxNodos], Tarco A)


{
return (Arcos[A.origen, A.destino]);
}

void InsertarConjArcos (int Arcos [][MaxNodos], Tarco A)


{
Arcos[A.origen, A.destino] = 1;
}

void BorrarConjArcos (int Arcos [][MaxNodos], Tarco A)


{
Arcos[A.origen, A.destino] = 0;
}

void AsignarConjArcos (int C1Arcos [][MaxNodos], int C2Arcos [][MaxNodos])


{
C2Arcos = C1Arcos;
}

Tarco ElegirConjArcos (int Arcos [][MaxNodos])


{ int i = j = 0;
Tarco A;

A.destino = MaxNodos;
do
{ do
{ if (Arcos[i, j])
{ A.origen = i;
A.destino = j;
Arcos[i, j] = 0;
}
j++;
} while ((A.destino == MaxNodos) && (j < MaxNodos));
i++;
j = 0;
} while ((A.destino == MaxNodos) && (j < MaxNodos));

return (A);
}

De la misma manera, las operaciones sobre el conjunto de vértices son:

void ConjVerticesVacio (int Vertices [])


{ int i;
for (i = 0; i < MaxNodos; i++)
Vertices [i] = 0;
}

int EsConjVerticesVacio (int Vertices [])


{ int i = hayvertices = 0;
do
{ hayvertices = Vertices[i] ;
i++;
} while ((!hayarcos) && (i < MaxNodos))

return (!hayarcos);
}
int PerteneceConjVertices (int Vertices [], Tvertice V)
{
return (Vertices[V]);
}

void InsertarConjVertices (int Vertices [], Tvertice V)


{
Vertices[V] = 1;
}

void BorrarConjVertices (int Vertices [], Tvertice V)


{
Vertices[V] = 0;
}

void AsignarConjVertices (int C1Vertices [], int C2Vertices [])


{
C2Vertices = C1Vertices;
}

Tvertice ElegirConjVertice (int Vertices [])


{ int i = 0;
Tvertice V = MaxNodos;
do
{ if (Vertices[i])
{ V = i;
Vertices[i] = 0;
}
i++;
} while ((V == MaxNodos) && (i < MaxNodos));

return (V);
}

Si se realiza la implementación de las operaciones sobre el tipo grafo teniendo en cuenta cómo están
implementados los conjuntos de vértices y arcos, se obtienen procesos más eficientes, pero esto implica una
dependencia total de la implementación de dichos conjuntos. Veamos dos ejemplos:

void ConjAdyacentes (Tgrafo G, Tvertice V, int Adyacentes [])


{ int i;

ConjVerticesVacio (Adyacentes);
for (i = 0; i < MaxNodos; i++)
if (G.arcos [V, i] Adyacentes [i] = 1;
}

Tgrafo BorrarVertice (Tgrafo G, Tvertice V)


{ int i;

G.vertices [V] = 0;
for (i = 0; i < MaxNodos; i++)
{ G.arcos [V, i] = 0;
G.arcos [i, V] = 0;
}

return (G);
}

Si el grafo es valorado o etiquetado, entonces los valores almacenados en la matriz representan el coste o
valor asociado a cada arco del grafo , en caso de que no existan los arcos en el grafo, en la matriz se
registra un coste ∞. Estas matrices, normalmente reciben el nombre de matrices de coste. Así por ejemplo,
si se trata de un grafo no dirigido, tanto A[i, j] como A[j, i] indican el coste del arco (vi,vj). Por el contrario,
si el arco (vi,vj) no existe en el grafo, entonces se asigna a A[i, j] como a A[j, i] un valor que no puede ser
utilizado como una etiqueta valida, indicando que dicho coste es ∞ .

Las ventajas que se pueden destacar de la matriz de adyacencia son:

o El orden de eficiencia de las operaciones de obtención del coste asociado a un arco

o La comprobación de conexión entre dos vértices cualesquiera es independiente del número de


vértices y de arcos del grafo.

Por el contrario, justificando la existencia otra representación, hay dos grandes inconvenientes:
• Es una representación orientada hacia grafos que no modifica el número de sus vértices ya que una
matriz no permite que se les supriman filas o columnas.

• Se puede producir un gran derroche de memoria en grafos poco densos (con gran número de vértices
y escaso número de arcos).

IMPLEMENTACIÓN DE GRAFOS MEDIANTE LISTAS DE ADYACENCIA

La realización de esta implementación es bastante más complicada que la anterior, pero se consiguen
complejidades mucho menores en alguno de los algoritmos que operan con esta estructura y se ahorra mucha
memoria.

Ahora el conjunto de arcos es una lista con tantos nodos como vértices tenga el grafo y cada uno de estos
nodos contiene una lista en la que están los vértices que son adyacentes al vértice correspondiente al nodo.
Es decir, en la lista i-ésima sólo estarán aquellos vértices adyacentes al vértice i-ésimo. Por tanto, la
existencia del arco [vi, vj] vendrá determinada por la pertenencia del vértice vj a la lista i-ésima.

Este grafo tiene la siguiente lista de adyacencia:

v0 V2 V0 V1 V4
V1

V1 V0 V2 V4

V3 V4 V5

V2 V1 V4

V3 V4

V4 V0 V1 V2 V3 V5

V5 V4

Así, el grafo queda completamente almacenado en una lista de vértices y cada nodo de la lista tiene enlazada
una lista con los vértices que son adyacentes. Con esta lista de adyacencia a través de la cual representamos
el conjunto de arcos, serviría para representar completamente el grafo, aunque para ajustarnos al TAD
definido, G = (V, E), debemos representar ambos conjuntos:
1. El conjunto de vértices será la lista simplemente enlazada cuyos elementos son los vértices del
grafo.
2. El conjunto de nodos donde cada arco toma como origen el vértice de la componente cabecera y
destino cada uno de los vértices de la lista de adyacencia correspondiente a ese origen.

La definición de tipos será:

typedef int Tvertice; // Información asociada a cada vértice.

typedef struct
{ Tvertice origen, destino;
} Tarco;

typedef struct NodoVertice


{ Tvertice v;
struct NodoVertice *sig;
} *TConjVertices;

typedef struct NodoArco


{ Tvertice v;
TConjVertices ady;
struct NodoArcos *sig;
} *TConjArcos;
typedef struct
{ TConjVertices vertices ;
TConjArcos arcos;
} Tgrafo;

Según estas declaraciones de tipos de datos, el conjunto de vértices se ha representado mediante una
enlazada y el conjunto de arcos mediante una lista de adyacencia.. La realización de los procesos
característicos del TAD conjunto utilizando listas de adyacencia es la siguiente:

void ConjArcosVacio (TConjArcos *Arcos)


{ *Arcos = NULL;
}

int EsConjArcosVacio (TConjArcos Arcos)


{ return (Arcos == NULL);
}

int PerteneceConjArcos (TConjArcos Arcos, Tarco A)


{ int encontrado = 0;
while ((! EsConjArcosVacio(Arcos)) && (encontrado = 0))
{ if (Arcos->v == A.origen)
encontrado = PerteneceListaVertices (Arcos->ady, A.destino);
if (encontrado = 0)
Arcos = RestoListaArcos(Arcos);
}
return (encontrado);
}

void InsertarConjArcos (TConjArcos *Arcos, Tarco A)


{ TConjArcos ant, Aux;
int encontrado = 0 ;

if (EsConjArcosVacio(*Arcos))
{ InsertarListaArcos(Arcos, A.origen);
InsertarListaVertices(Arcos->ady, A.destino);
}
else
{ Aux = *Arcos;
while ((!EsConjArcosVacio (Aux)) && (encontrado = 0))
{ if (Aux->v = A.origen)
{ encontrado = 1 ;
InsertarListaVertices (&Aux->ady, A.destino) ;
}
else
{ ant = Aux ;
Aux = RestoListaArcos(Aux) ;
}
}

if (encontrado = 0)
{ InsertarListaArcos (&(ant->sig), A.origen);
ant = ant->sig;
InsertarListaVertices(&(ant->ady), A.destino);
}
}
}

void BorrarConjArcos (TConjArcos *Arcos, Tarco A)


{ int encontrado = 0;
TConjArcos aux = *Arcos;

while ((!EsConjArcosVacio(aux)) && (encontrado = 0))


{ if (aux->v = A.origen)
{ encontrado = 1;
BorrarListaVertices (&aux->ady, A.destino);
}
aux = RestoListaArcos(aux);
}
}

void AsignarConjArcos (TConjArcos Arcos1, TConjArcos *Arcos2)


{ *Arcos2 = CopiarListaArcos(Arcos1);
}

Tarco ElegirConjArcos (TConjArcos *Arcos)


{ Tarco A;
TConjArcos aux;
int encontrado = 0;

if (EsConjArcosVacio(*Arcos))
{ puts ("ERROR, el conjunto de arcos está vacío");
exit(1);
}
else
{ aux = *Arcos;
while ((!EsConjArcosVacio(aux)) && (encontrado = 0))
{ A.origen = aux->v;
if( !EsConjVerticesVacio(aux->ady))
{ encontrado = 1;
A.destino = ElegirConjVertices (&(aux->ady));
}
else aux = RestoListaArcos(aux);
}
return (A);
}
}
void ConjVerticesVacio (TconjVertices *vertices)
{ *vertices = NULL;
}

int EsConjVerticesVacio (TConjVertices Vertices)


{ return (Vertices == NULL);
}

int PerteneceConjVertices (TConjVertices Vertices, Tvertice V)


{ int encontrado = 0;

while ((! EsConjVerticesVacio(Vertices)) && (encontrado = 0))


{
encontrado = (Vertices->v == V)
Vertices = RestoListaVertices(Vertices);
}
return (encontrado);
}

void InsertarConjVertices (TConjVertices *Vertices, Tvertice V)


{ InsertarListaVertices(Vertices, V);
}

void BorrarConjVertices (TConjVertices *Vertices, Tvertice V)


{ BorrarListaVertices (Vertices, V);
}

void AsignarConjVertices (TConjVertices Vertices1, TConjVertices *Vertices2)


{ *Vertices2 = CopiarListaVertices(Vertices1);
}

Tvertice ElegirConjVertices (TConjVertices *Vertices)


{ Tvertice V;

if (EsConjVerticesVacio(*Vertices))
{ puts ("ERROR, el conjunto de vértices está vacío");
exit(1);
}
else
{ V = PrimeroListaVertices (*Vertices) ;
BorrarListaVertices(Vertices, V) ;
}
return (V);
}

Al igual que se comentó en el caso de la representación con matriz de adyacencia, si se realizan las
operaciones directamente con la implementación de lista de adyacencia, generalmente se optimiza el
proceso. Así por ejemplo:

void ConjAdyacentes (Tgrafo G, TConjVertices *C, Tvertice V);


{ TConjArcos aux;
TConjVertices C;
int encontrado = 0;

ConjVerticesVacio( C );
aux = ConjArcos (G);

while ( (! EsConjArcosVacio(aux)) && (encontrado = 0))


{ if ( aux->v = V)
{ encontrado = 1;
C = AsignarConjVertices (aux->ady);
}
aux = RestoListaArcos(aux);
}
}

Otra forma de representar esta estructura de datos es por medio de un vector, donde cada componente sea
la lista de adyacencia correspondiente a cada uno de los vértices del grafo. Cada elemento de la lista consta
de un campo indicando el vértice adyacente.

Si se trata de un grafo valorado o etiquetado, para cualquiera de las realizaciones propuestas, habrá que
añadir un nuevo campo a cada vértice adyacente, para almacenar el coste de los arcos.

Das könnte Ihnen auch gefallen