Escolar Documentos
Profissional Documentos
Cultura Documentos
1. Definiciones básicas:
a b
Figura 1 Grafo
dirigido
G ={V , A} donde
V = { v1 , v 2 , , v n } ,
A = { a1 , a 2 , , a n } y a =(v
i j , vk ) tal que v j , v k ∈V . En dicho grafo se
entiende que (vi , v j ) ≠ (v j , vi ) y en muchos casos solo existe uno de los
pares de vértices.
Un vértice que solo tiene arcos saliendo de él se denomina
fuente y un vértice que solo tiene arcos dirigidos hacia él se
denomina sumidero. Dicha nomenclatura es importante cuando los
dígrafos se usan para resolver problemas de flujos.
a b
Figura 2 Grafo no
dirigido
G ={V , A} donde
V = { v1 , v 2 , , v n } , A = { a1 , a 2 , , a n } y ai =(v j , v k ) tal que v , v ∈V . En
j k
1 2 3 4 5
1 0 1 0 0 1
2 1 0 1 1 1
3 0 1 0 1 0
4 0 1 1 0 1
5 1 1 0 1 0
1 2
5 4
2 5 /
1 5 3 4 /
2 4 /
2 5 3 /
4 1 2 /
void limpia_grafo(){
int i, j;
for(i = 0; i < nvert; i++){
visitado[i] = 0;
for( j = i; j < nvert; j++)
grafo[i][j] = grafo[j][i] = 0;
}
}
Listado 1 Representación por matriz de adyacencia
void limpia_grafo(){
int i;
for(i = 0; i < nvert; i++){
grafo[i].clear();
visitado[i] = 0;
}
}
aux = grafo[j].begin();
fin = grafo[j].end();
while(aux != fin){
if(!visitado[*aux]){
pila.push(*aux);
visitado[*aux] = 1;
}
aux++;
}
Listado 3 Versión por listas de adyacencias
void limpia_grafo(){
int i, j;
for(i = 0; i < nvert; i++){
visitado[i] = 0;
grafo[i][i] = 0;
for( j = i+1; j < nvert; j++)
int main(){
int nodo1, nodo2;
scanf("%d\n", &casos);//leer numero de casos
while(casos>0){
scanf("%d %d\n", &aristas, &consultas);//leer numero de aristas y
//de consultas
fflush(stdin);
borra_grafo();//limpiar el grafo antes de leer las aristas
while(aristas > 0){//leer las aristas y almacenarlas en el grafo
scanf("%s %s\n", c1, c2);//leer arista como ciudad1 ciudad2
fflush(stdin);
nodo1 = c1[0] - 'A';//encontrar posicion en la lista de
nodo2 = c2[0] - 'A';//adyacentes en el grafo
grafo[nodo1].push_back(nodo2);//mete la arista en grafo no
grafo[nodo2].push_back(nodo1);//dirigido
aristas--;//actualizar numero de aristas por leer
}
return 0;
}
Listado 9 Caminos a partir del recorrido en anchura
ORDENAMIENTO_TOPOLOGICO(G)
• DFS(G) y calcular f[v] para cada v en G
• Conforme se termina de visitar un vértice insertar al frente de
una lista
• Devolver la lista como resultado
DFS(G)
• Para cada nodo u en G
o color[u] = blanco
o padre[u] = NULO
• tiempo = 0
• Para cada nodo u en G
o Si color[u] = blanco DFS-VISIT(u)
DFS-VISIT(u)
• color[u] = gris //se acaba de visitar el nodo u por primera vez
• tiempo = tiempo + 1
• d[u] = tiempo
• Para cada nodo v que se adyacente a u //explorar aristas (u,v)
o si color[v] = blanco
• padre[v] = u
• DFS-VISIT(v)
• color[u] = negro //se termino de visitar al nodo u y todos sus
adyacentes
• tiempo = tiempo + 1
• f[u] = tiempo
void limpia_grafo();
void DFS();
void DFS_VISIT(int);
//programa de prueba para los algoritmos DFS que aparecen en el cormen
int main(){
//variables para capturar el grafo
int na, origen, destino, i;
//capturar el numero de aristas en el grafo
scanf("%d\n", &na);
limpia_grafo();
while(na){
scanf("%d %d\n", &origen, &destino);
grafo[origen].push_back(destino);
na--;
}
return 0;
}
void DFS(){
int u;
0 1 2
3 4 5
Figura 5
Grafo para
probar listado
10
La salida
del programa del listado 10 es la siguiente:
[jorge@localhost ~]$ ./dfs_cormen<dfs_cormen.in
arreglo d
d[0] = 1, d[1] = 2, d[2] = 9, d[3] = 4, d[4] = 3, d[5] = 10,
arreglo f
f[0] = 8, f[1] = 7, f[2] = 12, f[3] = 5, f[4] = 6, f[5] = 11,
void ORDENAMIENTO_TOPOLOGICO(){
list<int>::iterator aux;//iterador para recorrer la lista con resultados
SCC(G)
1. llamar DFS(G) para calcular los f[u] para cada u en G
2. encontrar T
3. llamar DFS(T), pero en el ciclo principal de DFS, los vértices
se exploran en orden decreciente del f[u] calculado en el paso
1
4. Sacar los vértices de cada árbol generado en el paso 3 como un
componente fuertemente conexo por separado.
A continuación se muestran las modificaciones necesarias al
DFS para implementar el algoritmo anterior. Debe notarse que el
orden decreciente de los f[u] calculados en el paso 1, corresponden
al ordenamiento topológico de los vértices en G. Por lo que el paso 1
se puede sustituir por obtener el ordenamiento topológico de G. Y en
el paso 3 diríamos que se recorre el grafo en profundidad usando el
ordenamiento topológico calculado en el paso 1. También se incluye
una implementación para encontrar el transpuesto de un grafo.
//transpuesto de un grafo G con nv vertices, el resultado es el grafo T
void transpuesto(){
list<int>::iterator aux;
int i;
//borrar el grafo T antes de comenzar
for(i = 0; i < nv; i++)
T[i].clear();
for(i = 0; i < nv; i++){
for(aux = G[i].begin(); aux != G[i].end(); aux++)
T[*aux].push_back(i);
}
}
orden.clear();//borrar la lista
//calcular los f[u] con el DFS
DFS();
}
void limpia_grafo(){
cont = 0;
for(i = 0; i < 30; i++){
LOW[i] = visitado[i] = dfs_number[i] = padre[i] = 0;
grafo[i].clear();
}
T.clear();
articulaciones.clear();
}
MST-KRUSKAL(G,w)
1. A es el conjunto vacío
2. para cada vértice v en G
• make-set(v)
3. ordenar las aristas de menor a mayor peso
4. para cada arista (u,v) en G, en tomadas en orden creciente
• si find-set(u) es diferente de find-set(v)
o A es la union de A con (u,v)
o union(u,v)
5. Devolver A
Las operaciones en negritas corresponden a la implementación
de UNION-FIND. El resultado del algoritmo es el árbol de
expansión representado por el conjunto de aristas incluidas en A. A
continuación se muestra una implementación basada en la STL. Esta
implementación se probó con el grafo que aparece en la figura 23.4
del libro de Cormen.
#include<stdio.h>
#include<vector>
#include<algorithm>
//implementación de UNION-FIND
#define MAX 1000 // ajustarlo apropiadamente (tamaño máximo del conjunto)
int p[MAX], rank[MAX];
void make_set(int x)
{
p[x] = x;
rank[x] = 0;
}
int find_set(int x)
{
if (x != p[x])
p[x] = find_set(p[x]);
return p[x];
}
//algoritmo de kruskal
void kruskal(){
ARISTA a;
int i, j;//contadores de aristas y vertices
int u, v;//vertices
for(v = 0; v < nvert; v++)
make_set(v);
sort(G.begin(), G.end());
for(i = 0, j = 0; i < narist; i++){
a = G[i].second;
u = a.first;
v = a.second;
if(find_set(u) != find_set(v)){
A[j].first = G[i].first;
A[j++].second = a;
union_set(u,v);
}
}
}
int main(){
int i, n;//contadores
int u, v, w;//datos de las aristas
ARISTA a;//arista
ARISTA_PONDERADA ap;//arista ponderada
//implementación de UNION-FIND
#define MAX 1000 // ajustarlo apropiadamente (tamaño máximo del conjunto)
int p[MAX], rank[MAX];
int nconj;
void make_set(int x)
{
p[x] = x;
rank[x] = 0;
}
int find_set(int x)
{
if (x != p[x])
p[x] = find_set(p[x]);
return p[x];
}
//algoritmo de kruskal
void kruskal(){
ARISTA a;
int i;//contadores de aristas y vertices
int u, v;//vertices
int main(){
vector< pair<int, int> > edificios;
pair<int , int> edificio;
int i, j, x, y, existentes;
int x1, y1, x2, y2;
int u, v;
long w;
ARISTA a;
ARISTA_PONDERADA ap;
//borrar el grafo
G.clear();
//generar el grafo
for(i = 0, narist = 0; i < nvert; i++){
x1 = edificios[i].first;
y1 = edificios[i].second;
for(j = i+1; j < nvert; j++){
x2 = edificios[j].first;
y2 = edificios[j].second;
w = distancia( x1, y1, x2, y2);
a.first = i; a.second = j;
ap.first = w;
ap.second = a;
G.push_back(ap);
narist++;
}
}
//imprimir el resultado
printf("%.2llf\n", longitud);
}
return 0;
}
PRIM(G, r)
1. para cada vértice u en G
• clave[u] = infinito
• padre[u] = NULO
2. clave[r] = 0
3. Meter los vértices u de G a una cola de prioridad Q con clave[u]
4. Mientras no este vacía Q
• Extraer un vértice de Q y llamarlo u
• Para cada vértice v que sea adyacente a u
o Si v esta en Q y el peso de (u,v) < clave[v]
padre[v] = u
clave[v] = w(u,v)
#define NVERT 9//se usa la figura 23.5 del cormen como prueba
//inicializar el algoritmo
for(u = 0; u < NVERT; u++){
clave[u] = INFINITO;
padre[u] = NULO;
visitado[u] = 0;
}
clave[r] = 0;
visitado[r] = 1;
int main(){
int i, n;
int u, v, w;
ARISTA_PONDERADA ap;
Inicialización(G, s)
1. para cada vértice v en G
• d[v] = INFINITO
• padre[v] = NULO
2. d[s] = 0
Relajamiento(u,v)
1. si d[v] > d[u] + peso(u,v)
• d[v] = d[u] + peso(u,v)
• padre[v] = u
BELLMAN-FORD(G,s)
1. Inicializacion(G,s)
2. para i = 1 hasta nvert – 1
• para cada arista (u,v) en G relajamiento(u,v)
3. para cada arista (u,v) en G
• si d[u] > d[u] + peso(u,v) return FALSO
4. return VERDADERO
Camino(u,v)
1. si u = v imprime u
2. en caso contrario si padre[v] = NULO
• no existe camino de u a v
3. si padre[v] es diferente de NULO
• camino(u, padre[v])
• imprime v
Rutas-cortas-dags(G, s)
1. Ordenar topológicamente G
2. Inicialización(G,s)
3. para cada vértice u, ordenado topológicamente
• para cada vértice v que es adyacente a u Relajamiento(u,v)
DIJKSTRA(G,s)
1. Inicialización(G,s)
2. S es el conjunto vacío
3. Meter los vértices u a una cola de prioridad Q de acuerdo a d[u]
4. mientras Q no este vacía
• extraer el minimo de Q en u
• S = S union {u}
• para cada v adyacente a u relajamiento(u,v)
La implementación del algoritmo de Dijkstra es muy similar a
la del algoritmo de Prim. El conjunto S representa a los vértices ya
visitados por el algoritmo y que por tanto no serán incluidos en la
cola de prioridad. El procedimiento de relajamiento deberá sin
embargo actualizar las distancias de todos los nodos adyacentes a u,
sin importar si fueron o no visitados con anterioridad. El algoritmo
de Dijkstra no funciona para grafos con aristas negativas sin
importar si existen o no ciclos negativos. A continuación se presenta
una implementación basada en las colas de prioridad de la STL,
nótese que es casi idéntica a la implementación de Prim.
#include<stdio.h>
#include<queue>
#include<vector>
#include<list>
#define NVERT 9
//inicializar el algoritmo
for(u = 0; u < NVERT; u++){
d[u] = INFINITO;
padre[u] = NULO;
visitado[u] = 0;
}
d[s] = 0;
visitado[s] = 1;
//inicializar el algoritmo
for(u = 0; u < NVERT; u++){
d[u] = INFINITO;
padre[u] = NULO;
visitado[u] = 0;
}
d[s] = 0;
visitado[s] = 1;
int main(){
ARISTA_PONDERADA ap;
int num_calles, n;
int minimos[NVERT];
char usuario, dir, ciudad1, ciudad2, sha, mig;
int peso;
int min, i;
while(1){
ap.first = peso;
if(usuario == 'Y'){
ap.second = ciudad2 - 'A';
GMenores[ciudad1 - 'A'].push_back(ap);
if(dir == 'B'){
ap.second = ciudad1 - 'A';
GMenores[ciudad2 - 'A'].push_back(ap);
}
}else{
ap.second = ciudad2 - 'A';
GMayores[ciudad1 - 'A'].push_back(ap);
if(dir == 'B'){
ap.second = ciudad1 - 'A';
GMayores[ciudad2 - 'A'].push_back(ap);
}
}
n--;
}
//leer consulta
scanf("%c %c\n", &sha, &mig);
return 0;
}
Listado 17 Solución del problema 10171
La solución del listado anterior puede mejorarse en tiempo si
hacemos que el ciclo principal de Dijkstra termine cuando todos los
nodos estén marcados como visitados, para ello basta con llevar un
contador y encontrar la forma de contar el número de vértices. En el
caso de este problema se podría llevar un arreglo de banderas que se
ponga a 1 cada vez que un vértice se añade al grafo y luego se
contarían los 1’s para saber cuantos vértices existen en el grafo. Las
banderas tendrían que inicializarse a 0. El tiempo aún sin la mejora
que se menciona es bastante bueno (0.002 segundos) por lo que en
este caso no vale la pena, sin embargo puede ser útil al resolver un
problema con casos más grandes.
0 si i = j
∞ si i ≠ j y (i,j)
El algoritmo permite la presencia de aristas negativas siempre
que no existan ciclos negativos como sucede con el algoritmo de
Bellman-Ford. El método consiste en hacer iteraciones del proceso
de relajación para cada arista en el grafo, tomando en cada paso un
vértice intermedio diferente. La manera más simple de
implementarlo es por medio de tres ciclos anidados que iteran sobre
un grafo representado por una matriz de adyacencia. Al igual que los
otros métodos presentados con anterioridad es posible reconstruir el
camino más corto entre cualquier par de nodos por medio de un
procedimiento recursivo similar al presentado anteriormente. Para
reconstruir el camino es necesario tener una matriz de padres que se
actualiza durante el proceso de relajación. El algoritmo de Floyd se
presenta a continuación a manera de seudocódigo:
Floyd-Warshall
1. n = numero de vértices
2. para k = 1 to n
3. para i = 1 to n
4. para j = 1 to n
• si w(i,j) > w(i,k) + w(k,j)
o w(i,j) = w(i,k) + w(k,j)
o padre(i,j) = k
//matrices de pesos
int grafo_mayores[30][30], grafo_menores[30][30];
int main(){
//leer consulta
scanf("%c %c\n", &sha, &mig);
int W[NVERT][NVERT];
int Padre[NVERT][NVERT];
caso = 1;
while(1){
scanf("%d %d\n", &N, &R);
if(!N && !R) break;
NVERT = N;
inicializar();
for(i = 0; i < R; i++){
scanf("%d %d %d\n", &C1, &C2, &P);
inserta_arista( C1 - 1, C2 - 1, P);
inserta_arista( C2 - 1, C1 - 1, P);
}
//ejecutar el algoritmo
maxmin();
int W[NVERT][NVERT];
int Padre[NVERT][NVERT];
int NVERT;
int W[MAXVERT][MAXVERT];
int Padre[MAXVERT][MAXVERT];
int main(){
int caso;
int S, C, Q;
int c1, c2, d;
int minimo;
caso = 1;
while(1){
//validar la salida
if( minimo == INFINITO) printf("no path\n");
else printf("%d\n", minimo);
Q--;
}
}
}
FORD-FULKERSON( f, s)
1. Para cada arista (u, v) en el grafo
• f[u][v] = 0
• f[v][u] = 0
2. Mientras exista un camino de flujo residual entre f y s
• incremento = min(cap(u,v) tal que (u,v) está en el camino)
• para cada arista (u,v) en el camino
o f[u][v] = f[u][v] + incremento
o f[v][u] = -f[u][v]
Para comprender mejor el algoritmo anterior es necesario
definir algunos conceptos. Primero decimos que un grafo que
representa flujos es un grafo dirigido y ponderado, donde el peso de
las aristas representa una capacidad máxima de transportar un flujo.
El flujo residual es el flujo disponible en una determinada arista una
vez que se ha enviado flujo por ella (en ningún caso el flujo neto
residual debe ser mayor a la capacidad de dicha arista ni menor que
cero). El flujo residual lo calculamos como la capacidad –
flujo_actual, donde flujo_actual es el flujo que ya se ha ocupado en
alguna iteración del algoritmo. Un camino de flujo residual es aquel
camino de la fuente al sumidero donde todas las aristas en el camino
tienen un flujo residual mayor a cero.
//se considera que puede haber mas de una arista entre cada para de vertices
void inserta_arista(int origen, int destino, int capacidad){
grafo[origen][destino].capacidad += capacidad;
}
//inicializar la busqueda
for(u = 0; u < nvert; u++){
padre[u] = NULO;
visitado[u] = 0;
}
cola.clear();
//hacer la busqueda
visitado[fuente] = 1;
cola.push_back(fuente);
//ciclo principal de la busqueda por anchura
while(!cola.empty()){
//saca nodo de la cola
u = cola.front(); cola.pop_front();
for(v = 0; v < nvert; v++){
//elige aristas con flujo residual mayor a cero en el recorrido
residual = grafo[u][v].capacidad - grafo[u][v].flujo;
if(!visitado[v] && ( residual > 0)){
cola.push_back(v);//mete nodo a la cola
padre[v] = u;//guarda a su padre
visitado[u] = 1;//lo marca como visitado
}
}
}
//algoritmo de ford-fulkerson
int ford_fulkerson(int fuente, int sumidero){
int i, j , u;
int flujomax, incremento, residual;
//inicializar el grafo
inicia_grafo();
//leer la consulta
scanf("%d %d\n", &fuente, &sumidero);
flujo = ford_fulkerson(fuente, sumidero);
printf("El flujo maximo entre %d y %d es %d\n", fuente, sumidero, flujo);
return 0;
}
int G[MAXVERT][MAXVERT];
int nvert, narist, P[MAXVERT];
//inicializar la busqueda
for(u = 1; u <= nvert; u++){
visitado[u] = 0;
P[u] = NULO;
}
int main(){
int u, v, c;
int f, s;
int flujo, menor;
int n;
n = 1;
while(1){
//borrar el grafo
for(u = 1; u <= nvert; u++)
for(v = 1; v <= nvert; v++)
G[u][v] = 0;
return 0;
}
Listado 25 Solución del problema 820