Modulo II Algoritmo de Grafos Andres Perdomo CI 28164082

Descargar como pdf o txt
Descargar como pdf o txt
Está en la página 1de 43

Universidad Nacional Experimental de los Llanos Occidentales

Ezequiel Zamora – Barinas


Vicerrectorado de Planificación y Desarrollo Social
Programa Ingeniería, Arquitectura y Tecnología
Sub-Programa- Ingeniería. Informática
Sub-Proyecto Algoritmo y Programación III

MODULO II: ALGORITMOS SOBRE GRAFOS

Profesor: Ing. Julio Sánchez

Autor:
Br. Perdomo, Andrés C.I. 28.164.082
Sección: M01

Barinas, Mayo 2021


2

INTRODUCCION

Durante la preparación de este Módulo, perseguimos el propósito de conocer y


capacitarnos en conocimientos básicos relacionados con enfoque algorítmico del
tratamiento de grafos y dígrafos, así como el uso del computador para realizarlos a
través de los software conocidos o disponibles en la actualidad.

En el presente Módulo, se busca conocer los conceptos básicos y de cómo se


utilizan los grafos en el proceso de abstracción al momento de resolver un problema,
es decir, un grafo lo vemos como una herramienta matemática que nos permite
especificar formalmente un problema y hallar una solución al problema en términos
de una solución algorítmica en el grafo.

Para ello, desarrollaremos las definiciones conceptuales del contenido programático


correspondiente al presente Modulo II utilizando los conceptos disponibles en la
bibliografía y para el ejercicio se implementará en la codificación basados en el
Lenguaje de Programación HMTL, de acuerdo a las recomendaciones dadas por el
profesor.

Entre los conceptos a definir está el etiquetamiento, la búsqueda de profundidad y


de amplitud, los caminos y rutas críticas y sus aplicaciones.
3

Contenido

1. Recorrido en grafos.
a. Modelo general de etiquetamiento:
b. Algoritmo de Búsqueda en Profundidad (DFS) y Búsqueda en
Amplitud (BSF).
c. Aplicaciones.

2. Determinación de componentes conexas y fuertemente conexas.


a. Grafo reducido

3. Grafos de precedencia.
a. Partición en Niveles, Ordenamiento Topológico,
b. PERT.

4. Caminos de longitud k.

5. Caminos de longitud mínima


a. Ruta crítica

6. Árbol mínimo de Expansion


1. RECORRIDO EN GRAFOS.

El algoritmo de recorrido de grafos consiste básicamente en visitar un nodo del grafo y luego
ir visitando los nodos conectados a este. Este principio se aplica recursivamente comenzando
desde un nodo inicial cualquiera del grafo.

Lo que diferencia un algoritmo de recorrido de otro es, una vez ubicado en un nodo en
particular, la forma en que se visitan los nodos conectados a este. Por supuesto, estos
algoritmos pueden ser aplicados en grafos dirigidos o no dirigidos.

Los dos algoritmos “clásicos” de recorrido de grafos son el recorrido en profundidad y en


anchura.

a) MODELO GENERAL DE ETIQUETAMIENTO:

Los etiquetamientos de grafos han sido una herramienta poderosa en el estudio de las
propiedades de grafos y sus aplicaciones. Algunos ejemplos de etiquetamiento son los
coloramiento de los vértices y aristas, flujos, etiquetamientos elegantes, etc. Los grafos
etiquetamiento han servido como modelo para una variada cantidad de aplicaciones como:
flujos en redes, teoría de códigos, cristalografía con rayos-x, radares, astronomía, diseño de
circuitos. Comunicación en redes, manejo de base de datos. etc.

El mayor número de métodos de etiquetado de grafos tienen su origen en los conceptos


introducidos por Rosa en 1967 o Graham y Sloane en 1980. Rosa, llamó f a una función que
era una ß-evaluación de un grafo G con q aristas, donde f era una biyección de los vértices
de G a el conjunto {0, 1,..., q} tal que, cuando se le asigna a cada arista xy la etiqueta |f(x)-
f(y)|, las etiquetas resultantes son distintas. Posteriormente Golomb llamó a estas etiquetas
garbosas ("graceful") y ahora es el término utilizado más popular .

Basándonos en estos conceptos podemos dar una definición aproximada de que es un


etiquetado garboso

Podemos decir que hay dos tipos de etiquetados garbosos, el garboso y el garboso "perfecto":
5

Etiquetado Garboso

Dado un grafo con n vértices y q aristas, un etiquetado garboso sería aquel en el que se
etiqueta a las aristas con los números comprendidos entre 1 y q y los vértices con los números
comprendidos entre 0 y q y se tiene que cumplir que las diferencias de las etiquetas de los
vértices que delimitan cada arista es el mismo valor de la etiqueta de la arista.

Etiquetado Garboso Perfecto

Dado un grafo con n vértices y q aristas, un etiquetado garboso perfecto sería aquel en el que
se etiqueta a las aristas con los números comprendidos entre 1 y q y los vértices con los
números comprendidos entre 1 y n y se tiene que cumplir que las diferencias Etiquetado
Garboso Perfecto

Dado un grafo con n vértices y q aristas, un etiquetado garboso perfecto sería aquel en el que
se etiqueta a las aristas con los números comprendidos entre 1 y q y los vértices con los
números comprendidos entre 1 y n y se tiene que cumplir que las diferencias de las etiquetas
de los vértices que delimitan cada arista es el mismo valor de la etiqueta de la arista.
6

Etiquetado garboso de un árbol estrella

de las etiquetas de los vértices que delimitan cada arista es el mismo valor de la etiqueta de
la arista.

b) ALGORITMO DE BÚSQUEDA EN PROFUNDIDAD (DFS) y

Para efectuar un recorrido en profundidad de un grafo (DFS por sus siglas en inglés), se
selecciona cualquier nodo como punto de partida (por lo general el primer nodo del grafo) y
se marcan todos los nodos del grafo como “no visitados”. El nodo inicial se marca como
“visitado” y si hay un nodo adyacente a este que no haya sido “visitado”, se toma este nodo
7

como nuevo punto de partida del recorrido. El recorrido culmina cuando todos los nodos
hayan sido visitados.

Se dice que el recorrido es en profundidad, porque para visitar otro nodo adyacente del nodo
inicial, primero se deben visitar TODOS los nodos adyacentes al que se eligió antes. Es así,
como el número de ambientes recursivos varía dependiendo de la profundidad que alcance
el algoritmo.

Agregando el campo lógico visitado; en la última implementación de grafos tratada,


podríamos implementar el recorrido en profundidad de la siguiente manera:
Privado:
ACCION DFS_R(Nodo Actual)
#se marca el nodo actual como visitado
Actual.visitado verdad;
Ady aux;
aux Actual.ListaDeAdyacentes;
#se recorre la lista de adyacentes al nodo Actual para
#visitarlos
Mientras ( aux NULL ) hacer
si ( (aux.AdyAeste).visitado verdad) entonces
#se hace la llamada recursiva a los nodos
#adyacentes para visitarlos
DFS_R(aux. AdyAeste);
fsi
aux aux.proximo;
fmientras
FACCION
Publico:
ACCION DFS() #Depth-First Search
Nodo aux;
aux primerNodo;
#aquí se recorre la lista de nodos iterativamente
#haciendo las llamadas al DFS_R (DFS recursivo) cada vez que
#se consiga a alguien no visitado. Esto se hace para evitar
#que el algoritmo no funcione si el grafo tiene varias com-
#ponentes conexas
mientras (aux NULL) hacer
si (aux.visitado verdad) entonces
DFS_R(aux);
fsi
aux aux.proximo;
fmientras
FACCION
8

Este algoritmo recorre todos los nodos del grafo, pero fácilmente puede modificarse para que
sea una función que encuentre un nodo en particular dentro de grafo. Este algoritmo se
conoce como el algoritmo DFS (Depth-First Search).

Una bondad de este algoritmo es que los nodos solo se vistan una vez. Esto implica que si se
salvan en alguna estructura las aristas que se van recorriendo se obtiene un conjunto de aristas
de cubrimiento mínimo del grafo, lo cual se utiliza frecuentemente se utiliza para reducir la
complejidad del grafo cuando la perdida de información de algunas aristas no es importante.
Este resultado se conoce como árbol DFS (DFS Tree).

El algoritmo de recorrido en profundidad tiene orden O (máx. (A, N)) donde N es el número
de nodos y A es el número de aristas. Esto es porque un grafo de N nodos puede tener más
de N aristas, en cuyo caso se ejecutan más de N ciclos en DFS_R, pero si por el contrario hay
menos aristas que nodos, de cualquier manera, se visitaran todos los nodos en DFS.

El DFS puede modificarse fácilmente y utilizarse para resolver problemas sencillos como los
de conectividad simple, detección de ciclos y camino simple. Por ejemplo, el número de
veces que se invoca a la acción DFS_R desde la acción DFS en el algoritmo anterior es
exactamente el número de componentes conexas del grafo, lo cual representa la solución al
problema de conectividad simple.
9

c) ALGORITMO DE BÚSQUEDA EN AMPLITUD (BSF)

En este algoritmo también se utiliza la estrategia de marcas los nodos como “visitados” para
detectar la culminación del recorrido, pero los nodos se recorren de una manera ligeramente
distinta.

De nuevo, se selecciona cualquier nodo como punto de partida (por lo general el primer nodo
del grafo) y se marcan todos los nodos del grafo como “no visitados”. El nodo inicial se
marca como “visitado” y luego se visitan TODOS los nodos adyacentes a este, al finalizar
este proceso se busca visitar nodos más lejanos visitando los nodos adyacentes a los nodos
adyacentes del nodo inicial.

Este algoritmo puede crear menos ambientes recursivos que el anterior porque visita más
nodos en un mismo ambiente, pero esto depende de cómo este construido el grafo. El
algoritmo se conoce como el algoritmo de BFS (Breadth-First Search).

Este algoritmo tiene exactamente el mismo orden en tiempo de ejecución del algoritmo de
recorrido en profundidad y también se puede obtener el conjunto de aristas de cubrimiento
mínimo del grafo.

Una diferencia notable entre el DFS y el BFS es que este último necesita de una estructura
auxiliar, que por lo general es una cola, para el almacenamiento de las aristas que se van a
visitar durante el recorrido.

El siguiente ejemplo ilustra el funcionamiento del algoritmo BFS sobre un grafo de ejemplo.
La secuencia de ilustraciones va de izquierda a derecha y de arriba hacia abajo
10

Comenzamos introduciendo a la cola C todas las aristas adyacentes al nodo inicial (el nodo
0). Luego, extraemos la arista 0-2 de la cola y procesamos las aristas adyacentes a 2, la 0-2 y
la 2-6. No colocamos la arista 0-2 en la cola porque el vértice 0 ya fue visitado.

Luego, extraemos la arista 0-5 de la cola y procesamos las aristas adyacentes a 5. De manera
similar a la anterior, no se toma en cuenta la arista 0-5, pero si se encolan las aristas 5-3 y 5-
4. Seguidamente, extraemos la arista 0-7 y encolamos la arista 7-1. La arista 7-4 está impresa
en color gris porque si bien es un enlace adyacente a 7, podríamos evitar encolarla debido a
que ya existe una arista en la cola que nos lleva hasta el nodo 4.

Para completar el recorrido, tomamos las aristas que quedan en la cola, ignorando aquellas
que están impresas en color gris cuando queden de primeras en la cola. Las aristas entran y
salen de la cola en el orden de su distancia del vértice 0.

Al igual que en el DFS, usando el BFS se puede obtener un conjunto de aristas de cubrimiento
mínimo del grafo, conocido como el árbol (BFS Tree).
11

También puede modificarse fácilmente y utilizarse para resolver problemas sencillos como
los de conectividad simple, detección de ciclos y camino simple.

El BFS es el algoritmo clásico para encontrar el camino más corto entre dos nodos específicos
en un grafo, mientras que DFS nos ofrece muy poca ayuda para esta tarea debido a que el
orden en el que se visitan los nodos no tiene absolutamente ninguna relación con la longitud
de los caminos.

d.- APLICACIONES
Aplicación BFS y DFS
Se tienen dos sapos en la esquina derecha y dos ranas en la esquina izquierda con un espacio
intermedio que los separa, el objetivo es que los sapos queden en la esquina izquierda y las
ranas en la esquina derecha, las reglas son que solo se pueden mover hacia adelante y pueden
pasar una sobre la otra.

Para la resolución de este problema, se utilizan dos algoritmos Búsqueda en anchura (BFS)
y Búsqueda en profundidad (DFS).

Aplicación Breadth First Search (BFS)


Se modela el árbol que representa las posibles soluciones y se enumeran los nodos teniendo
en cuenta el algoritmo de Búsqueda en anchura.
12

Se realiza la prueba de escritorio

Aplicación Depth First Search (DFS)


13

Se modela el árbol que representa las posibles soluciones y se enumeran los nodos teniendo
en cuenta el algoritmo de Búsqueda en profundidad.

Se realiza la prueba de escritorio


2.- DETERMINACIÓN DE COMPONENTES CONEXAS Y FUERTEMENTE
CONEXAS GRAFO REDUCIDO

a) GRAFOS CONEXOS Un grafo es conexo si cada par de vértices está conectado por un
camino; es decir, si para cualquier par de vértices (a, b), existe al menos un camino
posible desde a hacia b. Un grafo es doblemente conexo si cada par de vértices está
conectado por al menos dos caminos disjuntos; es decir, es conexo y no existe un vértice
tal que al sacarlo el grafo resultante sea disconexo.

Es posible determinar si un grafo es conexo usando un algoritmo Búsqueda en anchura


(BFS) o Búsqueda en profundidad (DFS). En términos matemáticos la propiedad de un
grafo de ser (fuertemente) conexo permite establecer con base en él una relación de
equivalencia para sus vértices, la cual lleva a una partición de éstos en "componentes
(fuertemente) conexas", es decir, porciones del grafo, que son (fuertemente) conexas
cuando se consideran como grafos aislados. Esta propiedad es importante para muchas
demostraciones en teoría de grafos.

b) COMPONENTES FUERTEMENTE CONEXAS

Una componente fuertemente conexa de un dígrafo es un conjunto máximo de nodos en el


cual existe un camino que va desde cualquier nodo del conjunto hasta cualquier otro nodo
del conjunto. Si el dígrafo conforma una sola componente fuertemente conexa, se dice que
el dígrafo es fuertemente conexo.

Es sencillo desarrollar una solución de fuerza bruta para este problema. Solo se debe
comprobar para cada par de nodos u y v, si u es alcanzable desde v y viceversa. Sin embargo,
se puede utilizar el DFS para determinar con eficiencia las componentes fuertemente conexas
de una dígrafo.

· Método de Kosaraju
El método de Kosaraju es simple de explicar y de implementar. Sea G un dígrafo, a
continuación, se presenta una descripción del algoritmo de Kosaraju, a saber:
15

- Se ejecuta un DFS y se construye un dígrafo nuevo Gr, invirtiendo las direcciones de todos
los arcos de G. Esto es, se permutan los nodos del grafo en el orden definido por el recorrido
en post orden del mismo (para un dígrafo acíclico este proceso es equivalente a un
ordenamiento topológico)
. Se deben enumerar los nodos en el orden de terminación de las llamadas recursivas del DFS.
- Luego se ejecuta un DFS en Gr, partiendo del nodo con numeración más alta de acuerdo
con la numeración asignada en el paso anterior. Si el DFS no llega a todos los nodos, se inicia
la siguiente búsqueda a partir del nodo restante con numeración más alta.

- Cada árbol del conjunto de árboles DFS resultante es una componente fuertemente conexa
de G.

A continuación, se ilustra este método. La primera figura muestra el grafo G, la segunda


muestra el grafo Gr obtenido luego del primer paso del método y la última figura muestra las
dos componentes fuertemente conexas del grafo original G.

Se puede observar que en Gr las aristas estas invertidas. Los números que pueden verse a los
lados de los nodos son los asignados durante la terminación de las llamadas del DFS, donde
el nodo A fue el nodo inicial. La modificación del algoritmo original de DFS para lograr esta
numeración es sumamente sencilla.

Las componentes resultantes son el producto de un DFS en Gr comenzando con el nodo A y


luego otro DFS comenzando con el nodo D, siguiendo la heurística del segundo paso del
método descrito.
16

El método de Kosaraju encuentra las componentes fuertemente conexas de cualquier dígrafo


en tiempo y espacio lineal, mostrando una tremenda superioridad con respecto al algoritmo
de fuerza bruta descrito al comienzo de esta sección
3. GRAFOS DE PRECEDENCIA.

Un grafo de precedencia es un grafo dirigido sin circuitos. Muchas son las aplicaciones
donde encontramos grafos de precedencia. El concepto de orden entre los elementos de un
conjunto aparece con frecuencia en Computación.
(1) Podemos representar una expresión aritmética:
((a+b)*c + ((a+b)+e) * (e+f)) * ((a+b) * c)
(2) El grafo reducido de un grafo dirigido
(3) El grafo de una relación de orden parcial (eliminando los bucles).
(4) El grafo que representa un proceso de decisión secuencial como el de la figura V.6.
(5) Planificación de proyectos: un proyecto se puede dividir en actividades que deben
realizarse en un cierto orden. Tal es el caso de un pensum de estudios de una carrera
universitaria, donde ciertas materias son requisitos de otras. En este caso podemos
representar las materias por vértices de un grafo donde los arcos son los requisitos inmediatos
entre materias.

PROPIEDADES
Empezamos por dar dos propiedades sencillas pero importantes de grafos sin circuitos:
(a) Un grafo G no posee circuitos si y sólo sí todo subgrafo de G no posee circuitos.
(b) Principio de dualidad de grafos sin circuitos: Un grafo G no posee circuitos si y sólo si el
grafo inverso de G, G-1, obtenido a partir de G invirtiendo la orientación de todos los arcos,
no posee circuitos.
El estudio de grafos sin circuitos se fundamenta esencialmente en las propiedades siguientes:
Proposición
Sea G un dígrafo sin bucles. G no posee circuitos si y sólo si toda componente fuertemente
conexa es un vértice aislado.
Demostración:
Al haber una componente fuertemente conexa con más de un vértice es evidente que habrá
un circuito. Recíprocamente, si G posee un circuito, éste estará en una componente
fuertemente conexa.
Proposición
18

Un dígrafo G es de precedencia si y sólo si todo camino es elemental.


Demostración:
Que exista un camino no elemental es equivalente a decir que existe un camino cerrado. La
proposición nos dice que existe un camino cerrado si y sólo si existe un circuito.

Un vértice fuente de un dígrafo es un vértice con grado interior igual a cero, es decir, el
vértice es extremo inicial de todos los arcos incidentes en él. Un vértice sumidero es un
vértice con grado exterior igual a cero, es decir, el vértice es extremo terminal de todo arco
incidente en él.

Proposición
Todo grafo de precedencia G contiene un vértice fuente y un vértice sumidero.
Demostración:
Supongamos que G no contiene un vértice fuente. Entonces d-(G)³1 y la proposición nos dice
que G posee un circuito.
Por otro lado, si G es de precedencia, entonces el grafo inverso de G, G-1, es de precedencia
y por lo tanto G-1 posee una fuente, la cual es sumidero de G.

a. PARTICIÓN EN NIVELES

El problema de dividir un grafo es diversas particiones del mismo tamaño tiene muchas
utilidades en el área de la electrónica, así como en el desarrollo de sistemas operativos.
Este problema ha sido demostrado como un problema NP-difícil, lo que implica que
soluciones para él no pueden ser encontradas en tiempos razonables. El particionamiento
de grafos es un problema importante que tiene aplicaciones extensas en muchas áreas,
incluida la informática científica, el diseño de VLSI y la programación de tareas.

Problema. Considerando un Grafo donde es el conjunto de vértices y


de aristas, encontrar una partición en conjuntos de vértices con propiedades
específicas. Es decir que la salida es de la siguiente manera
19

Generalmente es considerado el problema como NP-Dificil , es decir son tan difíciles


como los NP-completos pero no precisamente están en NP.

P y NP son clases de complejidad utilizadas para clasificar los problemas de decisión. Un


problema de decisión se puede definir como un algoritmo que decide si no un determinado
elemento de entrada es parte de un conjunto dado . Un ejemplo de una decisión problema
es el problema del ciclo de Hamilton. Este problema determina si un gráfico dado pertenece
al conjunto de gráficos que contienen un ciclo que visita cada vértice exactamente una vez.

La clase P describe el conjunto de todos los problemas de decisión que se pueden resolver a
tiempo.

b. ORDENAMIENTO TOPOLÓGICO

El objetivo del ordenamiento topológico (topological sorting) es el de ser capaz de procesar


los nodos de un dígrafo acíclico (DAG – directed acyclic graph) de tal forma de que cada
nodo sea procesado antes que todos los nodos a los que apunta.
20

Hay dos maneras naturales de definir esta operación básica, aunque son esencialmente la
misma, a saber:

– Reetiquetado: Dado un DAG, reetiquetar sus nodos de tal forma que cada arista dirigida
vaya de un nodo con un identificador bajo en numero a un nodo con un identificador cuyo
identificador sea mayor.

– Reposicionado: Dado un DAG, reposicionar sus nodos en una línea horizontal de tal forma
de que todas sus aristas dirigidas apunte de izquierda a derecha.

Por lo general, se usa el término de ordenamiento topológico para referirse a la versión de


reposicionado.

DEFINICIÓN: Un orden topológico de un dígrafo G es un orden lineal de todos los vértices


tales que si G contiene un arco (U, V), entonces U aparece antes de V en el ordenamiento.

OBSERVACIÓN

 Si el grafo tiene ciclos el orden topológico no es posible.

 Los dígrafos son usados generalmente en aplicaciones para indicar la precedencia de eventos,
el ordenamiento topológico devuelve la posible secuencia válida en la deben realizarse dichos
eventos.

 Gráficamente se trata de poner todos los nodos en una línea de manera que sólo haya arcos
hacia adelante.

Una forma de encontrar un ordenamiento topológico es examinando todos los nodos del
dígrafo y aquellos nodos que no tengan aristas incidentes sobre ellos son eliminados del grafo
e introducidos en una cola (o lista), se repite este proceso hasta que no queden nodos en el
grafo.

1) Se escoge un vértice que no tenga lados incidentes y se coloca en una lista.


21

Escogemos el vértice 1, lo eliminamos del grafo. (al eliminar este vértice, se eliminan todos
los lados que salen de él)

2) Del grafo resultante, se escoge un vértice que no tenga lados incidentes en él, se quita del
grafo y se coloca en la cola. Escogemos el vértice 3, La cola es 1 3

3) Del grafo resultante, se escoge un vértice que no tenga lados incidentes en él, se quita del
grafo y se coloca en la cola 2, La cola es 1 3 2
22

Ahora sólo quedan los vértices 4, 5 de los cuales se escoge el vértice 4 ya que no tiene lados
incidentes en él y se coloca en la cola. Finalmente, el orden topológico queda: 1 3 2

c. PERT.

El grafo PERT se utiliza para calcular la duración del proyecto y para evaluar la importancia
de las diferentes tareas: Tiempo "early" = tiempo mínimo necesario para alcanzar un nudo.
Tiempo "last" = tiempo máximo que podemos tardar en alcanzar un nudo sin que el proyecto
sufra un retraso.
El método el PERT es un instrumento de programación temporal y toda programación
temporal requiere:
1 - Relacionar el conjunto de actividades que se ha de realizar.
2 - Estimar el tiempo que requiere cada una de ellas.
3 - Determinar el orden en el que han de realizarse las actividades, es decir, determinar las
precedencias existentes entre ellas.
Precisamente, una de las aportaciones del método es que obliga a identificar las actividades
que integran el proyecto, resaltando las dependencias y condicionamientos existentes entre
ellas, así como sus duraciones.
4.- CAMINOS DE LONGITUD K

La longitud de un camino es su número de aristas. Así, en un grafo no dirigido, los vértices


adyacentes están conectados por un camino de longitud 1, los segundos vecinos por
un camino de longitud 2, y así sucesivamente.

En términos más formales, se dice que un camino de longitud k desde u hasta w en G = (V,
E) es una secuencia de vértices 〈 v0, v1, …, vn 〉tal que v0 = u y vn = w, y que para todos
los vértices desde v0 hasta vn todas las aristas sean de la forma (vi-1, vi) ∈ E. Si esa trayectoria
existe, se dice que w es alcanzable desde u por dicho camino.
5.- CAMINOS DE LONGITUD MÍNIMA

CALCULO DEL CAMINO MAS CORTO

Todo camino en un dígrafo pesado tiene un peso asociado, el cual es la suma de los pesos de
las aristas del camino. Esta medida esencial nos permite formular problemas como el de
encontrar el camino con el menor peso entre dos vértices. El tópico de esta sección es el
cálculo de este tipo de camino, donde la longitud del camino no se mide en base al número
de aristas del mismo, sino en base al peso del camino.

Con esto último en mente, definiremos al camino más corto entre dos nodos de un dígrafo
pesado, como el camino dirigido que tenga la propiedad de tener el peso mínimo entre todos
los caminos que existan entre dicho par de nodos.

Se pueden plantear tres tipos de problemas:


- Camino más corto origen-destino: Dados dos nodos v y w de un grafo, encontrar el camino
más corto que comience en v y culmine w.
- Camino más corto a partir de un origen: Dado un nodo v de un grafo, encontrar el camino
más corto desde v hasta cada uno de los demás nodos.
- Camino más corto entre cada par de nodos.

· Algoritmo de Dijkstra
El algoritmo de Dijkstra resuelve el problema de encontrar los caminos más cortos a partir
de un origen, en grafos pesados que no tengan pesos negativos.

El algoritmo de Dijkstra es un algoritmo voraz que opera a partir de un conjunto S de nodos


cuya distancia más corta desde el origen ya es conocida. En principio, S contiene sólo el nodo
origen. En cada paso, se agrega algún nodo v a S, cuya distancia desde el origen es la más
corta posible. Bajo la hipótesis de que los pesos son no negativos, siempre es posible
encontrar un camino más corto entre el origen y v que pasa sólo a través de los nodos de S,
al que llamaremos “especial”. En cada paso del algoritmo, se utiliza un arreglo D para
registrar la longitud del camino “especial” más corto a cada nodo. Una vez que S incluye
25

todos los nodos, todos los caminos son “especiales”, así que D contendrá la distancia más
corta del origen a cada vértice. Se puede utilizar un arreglo P, para ir almacenando los
caminos más cortos.

A continuación se muestra un esbozo del algoritmo de Dijkstra en lenguaje pseudoformal :

#Supóngase la existencia del siguiente grafo


Conjunto V; # Conj. de nodos del grafo-N nodos
Arreglo C de real [1..N][1..N]; # Matriz de costos de las aristas
#y las siguientes variables auxiliares
entero i,w,v;
Arreglo D de real [1..N]; # arreglo auxiliar de costos
Arreglo P de entero [1..N]; # arreglo que guarda los caminos más
# cortos
Conjunto S; # Conjunto de nodos evaluados
# Inicializado con el conjunto vacío
#el esbozo del algoritmo de Dijkstra
S.Agregar(1); # tomando el nodo 1 como nodo origen
Para i 2 hasta N hacer
D[i] C[1][i]; # asignando valores iniciales a D
Fpara
Para i 1 hasta N-1 hacer
w nodo en V-S tal que D[w] sea un mínimo
S.Agregar(w);
Para cada nodo v V-S hacer
D[v] min (D[v], D[w]+C[w,v]);
Fpara
P[v] w;
Fpara

Al final de la ejecución, cada posición del arreglo P contiene el nodo inmediato anterior a v
en el camino más corto a partir del nodo inicial. Dado el grafo:

al final de la ejecución el arreglo P debe tener los


valores P[2]=1, P[3]=4 , P[4]=1 y P[5]=3. Para encontrar el camino más corto del nodo 1 al
26

nodo 5, por ejemplo, se siguen los predecesores en orden inverso comenzando en 5. Así, es
sencillo encontrar que el camino más corto del nodo 1 al 5, es el 1, 4, 3, 5.

Usando el arreglo P, o bien mediante alguna otra heurística, se puede construir fácilmente un
SPT, tomando el nodo inicial del recorrido como raíz del árbol y luego añadiendo una arista
y un nodo a la vez, siempre tomando la próxima arista que pertenezca al camino más corto a
un nodo que no esté en el SPT.

El orden en tiempo de ejecución del algoritmo de Dijkstra depende muchísimo de las


características del grafo y de cómo este almacenado. Si se emplea una matriz de adyacencia
como en el código anterior, entonces el ciclo más interno es de O(N) y como se ejecuta N-1
veces, tendríamos un algoritmo de O(N2). Si por el contrario se utiliza una representación
dinámica, justificada en el hecho de que el número de aristas es mucho menor a N2, se podría
emplear un árbol parcialmente ordenado para organizar los nodos V-S, y por lo tanto se
reduciría la complejidad del ciclo más interno un orden logarítmico.

Aunque se puede ejecutar Dijkstra N veces para resolver el problema de caminos cortos entre
cada par de nodos, el Algoritmo de Floyd [5] también resuelve este problema, con una
complejidad en tiempo similar.

a. RUTA CRÍTICA

Cuando estás gestionando un proyecto complejo con muchas piezas en juego y plazos que
cambian constantemente, puede ser difícil entregar un producto a tiempo y dentro del
presupuesto. Hay una amplia gama de herramientas de gestión de proyectos que pueden
ayudarte a lograr estos objetivos, una de ellas es el método de la ruta crítica.

El método de la ruta crítica es una técnica de modelado de proyectos que puedes usar para
analizar, planificar y programar proyectos complejos. Básicamente, el método de la ruta
crítica le exige que enumere todas las actividades que deben terminarse para completar un
proyecto, el tiempo que llevará realizar cada actividad y las dependencias entre estas
27

actividades. La ruta crítica en sí es la mayor cantidad de tiempo que te llevará completar el


proyecto general, lo que te permitirá saber cuál es la mejor manera de estructurar la
programación del proyecto para garantizar que se entregue a tiempo y con un costo mínimo.
En pocas palabras, el método de la ruta crítica te ayuda a comprender cuál es la línea de
tiempo óptima para completar tu proyecto.

La historia del análisis de la ruta crítica

El método de la ruta crítica fue desarrollado a fines de la década de 1950 por James E. Kelley
de Remington Rand y Morgan R. Walker de DuPont. Estaban intentando encontrar maneras
de reducir los costos asociados con los cierres y reinicios de plantas, causados por
programaciones ineficientes. Al garantizar que las tareas correctas se realizaran en los
tiempos adecuados, en lugar de simplemente saturar el problema con mano de obra adicional,
encontraron que se podían evitar los costos excesivos.

Kelley y Walker publicaron un artículo sobre su investigación en 1959, aunque DuPont


abandonó la técnica después de que el equipo de gerencia que era responsable de esta la
cambió. Casi al mismo tiempo, la Marina de los Estados Unidos y Booz Allen Hamilton
desarrollaron una técnica similar:⁠PERT (técnica de revisión y evaluación de programas),⁠ que
es donde surgió el término "ruta crítica". De hecho, el análisis de ruta crítica tiene sus raíces
en algunas de las técnicas de DuPont que se remontan a principios de la década de 1940 y
que contribuyeron al éxito del Proyecto Manhattan.

Si bien el interés de DuPont por el método de la ruta crítica había disminuido a principios de
la década del 60, algunas otras empresas comenzaron a usarlo para supervisar proyectos
grandes, incluidas Mauchly Associates y Catalytic Construction. Al principio, para usar el
método de la ruta crítica se necesitaba acceder a grandes computadoras centrales y
especializadas. Por este motivo, el costo de la gestión de proyectos con ruta crítica era un
obstáculo significativo para adoptar su uso. No obstante, después de la revolución de las
computadoras y las innovaciones en hardware/software informático que hicieron posible
realizar la gestión de programas en una computadora de escritorio estándar, el uso de la ruta
crítica se extendió mucho más
6.- ÁRBOL MÍNIMO DE EXPANSIÓN

Dado un grafo conexo, no dirigido G. Un árbol de expansión es un árbol compuesto por todos los
vértices y algunas (posiblemente todas) de las aristas de G. Al ser creado un árbol no existirán
ciclos, además debe existir una ruta entre cada par de vértices.

Un grafo puede tener muchos árboles de expansión, veamos un ejemplo con el siguiente
grafo:

En la imagen anterior se puede observar que el grafo dado posee 3 árboles de expansión,
dichos arboles cumplen con las propiedades antes mencionadas como son unir todos los
vértices usando algunas aristas.

Árbol de Expansión Mínima

Con un grafo conexo, no dirigido y con pesos en las aristas, un árbol de expansión mínima
es un árbol compuesto por todos los vértices y cuya suma de sus aristas es la de menor peso.
Al ejemplo anterior le agregamos pesos a sus aristas y obtenemos los arboles de expansiones
siguientes:
29

De la imagen anterior el árbol de expansión mínima sería el primer árbol de expansión cuyo
peso total es 6.

El problema de hallar el Árbol de Expansión Mínima (MST) puede ser resuelto con varios
algoritmos, los más conocidos con Prim y Kruskal ambos usan técnicas voraces (greedy).

Algoritmo de Kruskal

Para poder comprender el algoritmo de kruskal será necesario revisar primer el tutorial
de Union-Find.
Como trabaja:
Primeramente, ordenaremos las aristas del grafo por su peso de menor a mayor. Mediante la
técnica greedy Kruskal intentara unir cada arista siempre y cuando no se forme un ciclo, ello
se realizará mediante Union-Find. Como hemos ordenado las aristas por peso comenzaremos
con la arista de menor peso, si los vértices que contienen dicha arista no están en la misma
componente conexa entonces los unimos para formar una sola componente mediante Unión
(x , y), para revisar si están o no en la misma componente conexa usamos la función
30

SameComponent(x , y) al hacer esto estamos evitando que se creen ciclos y que la arista que
une dos vértices siempre sea la mínima posible.

Algoritmo en Pseudocódigo
1 método Kruskal(Grafo):
2 inicializamos MST como vacío
3 inicializamos estructura unión-find
4 ordenamos las aristas del grafo por peso de menor a mayor.
5 para cada arista e que une los vértices u y v
6 si u y v no están en la misma componente
7 agregamos la arista e al MST
8 realizamos la unión de las componentes de u y v

Ejemplo y código paso a paso


Tengamos el siguiente grafo no dirigido:

Como podemos ver en la imagen anterior la definición de nuestro grafo en código sería:
31

1
struct Edge{
2 int origen; //Vértice origen
3 int destino; //Vértice destino
4 int peso; //Peso entre el vértice origen y destino
5 Edge(){}

6 }arista[ MAX ]; //Arreglo de aristas para el uso en kruskal
7

Primeramente, usaremos el método MakeSet de unión-find para inicializar cada componente,


obteniendo las siguientes componentes conexas iniciales:

Ahora el siguiente paso es ordenar las aristas del grafo en orden ascendente:
32

Para el ordenamiento podemos usar las librerías predefinidas de Java y C++ como estamos
ordenando estructuras necesitamos un comparador, en este caso estamos ordenando por peso
por lo tanto dentro de la estructura antes definida agregamos:

1 struct Edge{
2 …
3 //Comparador por peso, me servira al momento de ordenar lo realizara en orden ascendente
4 //Cambiar signo a > para obtener el arbol de expansion maxima
5 bool operator<( const Edge &e ) const {
6 return peso < e.peso;
7 }
8 }arista[ MAX ]; //Arreglo de aristas para el uso en kruskal
Ordenamos el arreglo de aristas mediante lo siguiente:

1 std::sort( arista , arista + E ); //Ordenamos las aristas por su comparador

Lo siguiente será recorrer todas las aristas ya ordenadas y verificar si sus vértices están o no
en la misma componente.

La primera arista a verificar es la que une a los vértices 8 y 7, verificamos si están en la


misma componente, para ello hacemos Find(8) , Find(7):

Como podemos observar en la tabla y en la misma imagen no están en la misma componente


conexa, por tanto esta arista es valida para el MST así que unimos los vértices por el método
de Union( 8 , 7 ).
33

Continuamos con la siguiente arista:

Observamos la tabla de Union-Find y vemos que Find(3) != Find(9). Entonces es posible


realizar la unión de ambas componentes:

Continuamos con la siguiente arista:


34

En la imagen podemos observar que ambos vértices no están en la misma componente, por
tanto realizamos la Union( 6 , 7 ):

Continuamos con la siguiente arista, los vértices 1 y 2 no están en la misma componente


conexa:

Realizamos la Union(1,2):
35

Continuamos con la siguiente arista:

Al observar la imagen los vértices 3 y 6 están en distinta componentes conexas. Entonces


realizamos la Unión (3,6) y actualizamos la tabla de Union-Find.

Continuamos con la siguiente arista:


36

En este caso si observamos la imagen los vértices 7 y 9 están en la misma componente


conexa; asimismo en la tabla de Union-Find el elemento raíz del vértice 7 es el mismo que
el del vértice 9 por ello afirmamos que están el a misma componente conexa, por lo tanto no
habrá que realizar la unión de ambos vértices. Con esto evitamos tener ciclos en el árbol de
expansión mínima.
Continuamos con la siguiente arista:

Al observar la
imagen los vértices 3 y 4 no están en la misma componente conexa por lo tanto realizamos
la Union(3,4) en el grafo:
37

Continuamos con la siguiente arista:

Los vértices 8 y 9 están en la misma componente conexa por lo tanto no realizamos Unión
de vértices. Continuemos con la siguiente arista:

Los vértices 1 y 8 están diferentes componentes. Realizamos la Union(1,8) en el grafo:

Continuamos con la siguiente arista:


38

Los vértices 2
y 3 están en la misma componente conexa por lo tanto no realizamos Union de componentes.
Continuamos con la siguiente arista:

Los vértices 4 y 7 no están en la misma componente conexa, realizamos Union(4,5) en el


grafo:

Como podemos observar ya están todos los vértices del grafo conectados así que al momento
de continuar viendo las demás aristas ordenadas siempre tendremos e l caso de que ya están
39

en la misma componente conexa por lo tanto el Árbol de Expansión Mínima para el grafo es
el siguiente:

El peso total del árbol de expansión mínima para el grafo mostrado es 39.

En código simplemente es iterar sobre el arreglo de aristas ingresado y ordenado obteniendo


sus respectivos datos. Para verificar si están o no en la misma componente usamos el método
sameComponent explicado en el tutorial de Union-Find:

1 for( int i = 0 ; i < E ; ++i ){ //Recorremos las aristas ya ordenadas por peso
2 origen = arista[ i ].origen; //Vértice origen de la arista actual
3 destino = arista[ i ].destino; //Vértice destino de la arista actual
4 peso = arista[ i ].peso; //Peso de la arista actual
5 //Verificamos si estan o no en la misma componente conexa
6 if( !sameComponent( origen , destino ) ){ //Evito ciclos
7 total += peso; //Incremento el peso total del MST
8 MST[ numAristas++ ] = arista[ i ]; //Agrego al MST la arista actual
9 Union( origen , destino ); //Union de ambas componentes en una sola
10 }
11 }

Verificación de MST
Para que sea un MST válido el número de aristas debe ser igual al número de vértices – 1.
Esto se cumple debido a que el MST debe poseer todos los vértices del grafo ingresado y
además no deben existir ciclos. Si vemos el ejemplo antes explicado tenemos en el MST:

 Número de Aristas = 8
 Número de Vértices = 9
40

Cumple con lo dicho -> 9 – 1 = 8 por tanto tenemos un MST válido. Veamos otro ejemplo
teniendo como grafo ingresado lo siguiente:

Como podemos observar el grafo ingresado posee 2 componentes conexas, al aplicar kruskal
obtendremos los siguientes MST:

En la imagen podemos observar el MST luego de aplicar kruskal, sin embargo, no es un MST
válido porque no tiene 1 componente conexa que posea todos los vértices, comprobemos:

 Número de Aristas = 7
 Número de Vértices = 9
No cumple lo dicho inicialmente 9 – 1 ≠ 7 por lo tanto tenemos un MST invalido
CONCLUSION

Luego de realizar la investigación del contenido del Módulo II, podemos concluir que:

a) Que los Grafos son Abstracciones matemáticas muy útiles para la resolución de
problemas de ingeniería.
b) Que existen diversas técnicas para determinar la mejor ruta o la más eficiente, así
como la determinación de la ruta crítica, muy útil en el control de la ejecución de
proyectos de diversas índoles.
c) Que podemos utilizar diversos softwares en la implementación de los algoritmos para
el recorrido de Grafos y de grafos dirigidos, tales como Java, JavaScript y HTML,
Python etc.
REFERENCIAS BIBLIOGRAFICAS

1. Parra Muñoz, Jonathan. TEORIA DE GRAFOS. Universidad del Bio Bio.


Tesis de Grado. Chillan, Chile. 2013.
2. Coto, Ernesto. ALGORITMOS BASICOS DE GRAFOS. Universidad Central
de Venezuela. Editorial UCV. Caracas 2003.
3. Meza, Oscar. Ortega, Maruja. GRAFOS Y ALGORITMOS. Segunda Edición.
Universidad Simón Bolívar. Caracas. 2004.
4. Gravano, Agustín. ESTUDIO DE PROBLEMAS, PROPIEDADES Y
ALGORITMOS EN GRAFOS ARCO-CIRCULARES Y CIRCULARES. Tesis
de Licenciatura. Universidad de Buenos Aires. Buenos Aires 2001.
5. CONTRERAS SALINAS, FELIPE GUILLERMO. P-PARTICIONES
CONVEXAS EN UNA FAMILIA DE GRAFOS CONSTRUIDOS MEDIANTE
REEMPLAZOS. Tesis de Grado. Universidad de Chile. Santiago de Chile.
2016

1. http://fernandavia.blogspot.com/2018/08/aplicacion-bfs-y-dfs_26.html
2. https://jariasf.wordpress.com/2012/04/19/arbol-de-expansion-minima-
algoritmo-de-kruskal/
3. https://webs.um.es/pacovera/miwiki/lib/exe/fetch.php?id=inicio&cache=
cache&media=grafos.pdf.
4. https://www.editdiazdesantos.com/libros/marquez-garcia-alfonso-
miguel-guia-para-dibujar-los-grafos-pert-mas-complejos-sin-dificultad-
L30002200109.html.
5. https://compdiscretas.wordpress.com/2012/11/18/3-3-ordenamientos-
topologicos/
6. https://mathenki.wordpress.com/2018/06/07/problema-de-
particionamiento-de-grafos-algoritmos-heuristicos/
43

7. http://usandojava.blogspot.com/2012/02/escribiendo-y-leyendo-en-un-
archivo-
de.html#:~:text=Recorrido%20o%20b%C3%BAsqueda%20en%20profun
didad,manera%20ordenada%2C%20pero%20no%20uniforme.
8. http://usandojava.blogspot.com/2012/02/escribiendo-y-leyendo-en-un-
archivo-
de.html#:~:text=Recorrido%20o%20b%C3%BAsqueda%20en%20profun
didad,manera%20ordenada%2C%20pero%20no%20uniforme.

También podría gustarte