Anda di halaman 1dari 47

UNIDAD IV

4.3.2-Representación en la computadora de los grafos


Los grafos pueden tener distintas estructuras de datos, por lo que los
algoritmos pueden variar, dependiendo en cuales adoptemos dependerá el
tiempo de ejecución en función del número de vértices y aristas, todo lo antes
mencionado variará en dos casos: el grafo es denso (cuando tiene muchas
aristas) o disperso (muy pocas aristas).
Las representaciones más utilizadas de representación de los grafos son: a)
Representación por matriz de adyacencia b) Representación por listas de
adyacencias. La matriz de adyacencia es la forma más común de
representación y la más directa. Consiste en una tabla de tamaño nxn, en que
la que aij tendrá como valor 1 si existe una arista del vértice i al vértice j.
En caso contrario, el valor será 0. Si el grafo es no dirigido hay que asegurarse
de que se marca con un 1 tanto la entrada aij como la entrada aji, puesto que se
puede recorrer en ambos sentidos. Como se puede apreciar, la matriz de
adyacencia siempre ocupa un espacio de n2, es decir, depende solamente del
número de nodos y no del de aristas, por lo que será útil para representar
grafos densos.
Una lista de adyacencia consiste de una lista de los vértices del grafo y para
cada vértice de una lista de sus vértices vecinos, es definir una lista enlazada
para cada nodo, que contendrá los nodos a los cuales es posible acceder.
4.3.3-Algoritmo de Dijkstra
Es un algoritmo para la determinación del camino más corto dado un vértice
origen al resto de vértices en un grafo con pesos en cada arista. Su nombre se
refiere a Edsger Dijkstra, quien lo describió por primera vez en 1959.
-Es un algoritmo greddy, porque es una estrategia de búsqueda por la cual se
sigue una heurística consistente en elegir la opción óptima en cada paso local
con la esperanza de llegar a una solución general óptima.
-Trabaja por etapas, y toma en cada etapa la mejor solución sin considerar
consecuencias futuras.
-El óptimo encontrado en una etapa puede modificarse posteriormente si surge
una solución mejor.
Mejor explicado:
Para solucionar el problema de la ruta más corta entre dos nodos de un grafo
se puede utilizar el algoritmo de Dijkstra, el cual sigue el siguiente
procedimiento para calcular la ruta más corta desde el nodo origen hasta cada
uno de los nodos del grafo:
· Crear una lista de nodos para almacenar los nodos con distancia mínima
ya calculada
· Crear una cola de prioridad para los nodos pendientes de evaluar
· Inserta el nodo de origen a la cola de prioridad
· Mientras que haya nodos en la cola de prioridad

ºextrae el primer nodo de la cola de prioridad (tmp)


ºagrega el nodo tmp a la lista de nodos ya calculados
ºgenera una lista de los nodos conectados al nodo tmp que no estén en la lista
de ya calculados
º Para cada nodo de la lista (nod)

▪ calcula la distancia al origen con la distancia entre tmp y nod mas la


distancia calculada entre el origen y tmp.
▪ Si el nodo nodestá en la cola de prioridad lo agrega
▪ Si el nodo nod ya está en la cola de prioridad y la distancia con la que está
guardado en la cola es menor, lo deja como esta y si no, lo actualiza con la
distancia calculada.
º Fin
· Fin
¿Qué tipos de redes pueden resolverse por medio del Algoritmo de Dijkstra?

Este algoritmo, al igual que el método de Floyd, tienen la capacidad de resolver


el problema de la ruta más corta, tanto para redes cíclicas, como para redes
acíclicas. De manera tal que los bucles que presente una red, no restringen el
uso del algoritmo.

El algoritmo de Dijkstra hace uso y define etiquetas a partir del nodo origen y
para cada uno de los nodos subsiguientes. Estas etiquetas contienen
información relacionada con un valor acumulado del tamaño de los arcos y con
la procedencia más próxima de la ruta.

Las etiquetas corresponden a los nodos, no a los arcos. En el algortimo de


Dijkstra, estas etiquetas son temporales y permanentes. Las etiquetas
temporales son aquellas que son susceptibles de modificarse mientras exista la
posibilidad de hallar para sí, una ruta más corta; de lo contrario, dicha etiqueta
pasa a ser permanente.

Etiqueta = [acumulado, nodo procedente] iteración

Algoritmo de Dijkstra - Ejemplo


Teniendo en cuenta la siguiente red, que tiene como origen el nodo 1:
Procedemos con las iteraciones, partimos de la iteración 0, es decir, asignar la
etiqueta para el nodo origen:
Iteración 0

En este paso, asignamos una etiqueta permanente para el nodo


origen. Recordemos que la etiqueta se compone por un valor acumulado, que
en este caso debe ser 0, ya que es el punto base y que no existe procedencia:

Etiqueta nodo 1 = [valor acumulado, procedencia] iteración


Etiqueta nodo 1 = [0, -] 0

Iteración 1

En este paso, evaluamos a cuáles nodos se puede llegar desde el nodo 1. En


este caso se puede llegar a los nodos 2 y 3. De manera que debemos asignar
las etiquetas para cada nodo:

Etiqueta nodo 2 = [valor acumulado, procedencia] iteración


Etiqueta nodo 2 = [0 + 100, 1] 1

0 es el valor acumulado en la etiqueta del nodo de procedencia, en este caso el


valor acumulado para el nodo 1. 100 es el valor del arco que une el
nodo procedencia (1) y el nodo destino (2). 1 es el número de la iteración.

Etiqueta nodo 3 = [valor acumulado, procedencia] iteración


Etiqueta nodo 3 = [0 + 30, 1] 1

En este caso, podemos observar que la etiqueta del nodo 3, ya contiene el


menor valor acumulado posible para llegar a este. Ya que 30 es la mínima
distancia posible, toda vez que para llegar al nodo 3 por medio del nodo 2,
tendrá que recorrer como mínimo el valor absoluto del arco que une el origen
con el nodo 2 = 100. Así entonces, la etiqueta del nodo 3 pasa a ser
permanente.

Tabulamos la iteración 1:
Iteración 2:

En este paso, evaluamos las posibles salidas desde el nodo 3, es decir los
nodos 4 y 5. De manera que debemos asignar las etiquetas para cada nodo:

Etiqueta nodo 4 = [valor acumulado, procedencia] iteración


Etiqueta nodo 4 = [30 + 10, 3] 2
Etiqueta nodo 4 = [40, 3] 2

30 es el valor acumulado en la etiqueta del nodo de procedencia, en este caso


el valor acumulado para el nodo 3. 10 es el valor del arco que une el
nodo procedencia (3) y el nodo destino (4). 2 es el número de la iteración.

Etiqueta nodo 5 = [valor acumulado, procedencia] iteración


Etiqueta nodo 5 = [30 + 60, 3] 2
Etiqueta nodo 4 = [90, 3] 2
30 es el valor acumulado en la etiqueta del nodo de procedencia, en este caso
el valor acumulado para el nodo 3. 60 es el valor del arco que une el
nodo procedencia (3) y el nodo destino (5). 2 es el número de la iteración.

En este caso, podemos observar que la etiqueta del nodo 4, contiene el menor
valor acumulado posible para llegar a este. Así entonces, la etiqueta del nodo 4
pasa a ser permanente.

Tabulamos la iteración 2:

Iteración 3:

En este paso, evaluamos las posibles salidas desde el nodo 4, es decir los
nodos 2 y 5. De manera que debemos asignar las etiquetas para cada nodo:
Etiqueta nodo 2 = [valor acumulado, procedencia] iteración
Etiqueta nodo 2 = [40 + 15, 4] 3
Etiqueta nodo 2 = [55, 4] 3

40 es el valor acumulado en la etiqueta del nodo de procedencia, en este caso


el valor acumulado para el nodo 4. 15 es el valor del arco que une el
nodo procedencia (4) y el nodo destino (2). 3 es el número de la iteración.

Etiqueta nodo 5 = [valor acumulado, procedencia] iteración


Etiqueta nodo 5 = [40 + 50, 4] 3
Etiqueta nodo 5 = [90, 4] 3

40 es el valor acumulado en la etiqueta del nodo de procedencia, en este caso


el valor acumulado para el nodo 3. 50 es el valor del arco que une el
nodo procedencia (4) y el nodo destino (5). 3 es el número de la iteración.

En este caso, podemos observar que el nodo 2 ahora cuenta con 2 etiquetas
temporales, y definitivas, ya que no existe otra ruta hacia dicho nodo. De
manera que se elige la etiqueta que tenga el menor valor acumulado. Así
entonces, la etiqueta del nodo 2 con procedencia del nodo 4, pasa a ser
permanente.

Tabulamos la iteración 3:
Iteración 4:

En este paso, evaluamos las posibles salidas desde el nodo 2 y el nodo 5. Sin
embargo, el nodo 2 solo tiene un posible destino, el nodo 3, el cual ya tiene una
etiqueta permanente, de manera que no puede ser reetiquetado. Ahora,
evaluamos el nodo 5 y es un nodo que no tiene destinos. Así entonces, su
etiqueta temporal pasa a ser permanente, en este caso cuenta con 2 etiquetas
que tienen el mismo valor, es decir, alternativas óptimas. De esta manera
concluye el algortimo de Dijkstra.
¿Cuál es la ruta más corta?

La ruta más corta entre el nodo 1 (origen) y cualquier otro nodo de la red
(destino), se determina partiendo desde el nodo destino y recorriendo las
procedencias de sus etiquetas. Por ejemplo:

¿Cuál es la ruta más corta entre el nodo 1 y el nodo 4?

En este caso, debemos partir desde la etiqueta del nodo 4.


La ruta más corta entre el nodo 1 y el nodo 4 tiene un valor acumulado de: 40

Es necesario considerar, que los valores utilizados en los arcos de la red objeto
de estudio del algoritmo de Dijkstra, no necesariamente representan distancias.
Si bien, es un modelo que aborda el denominado "problema de la ruta más
corta"; en la práctica, puede utilizarse para optimizar: distancia, costos, tiempo.

De hecho, los sistemas de ruteo utilizados en la actualidad, priorizan los costos


y el tiempo como variable decisión de los modelos de optimización.
Ejemplo en código de C++
1. #include <iostream>
2. #include <iomanip>
3. #include <list>
4.
5. /* Definiendo la estructura etiqueta de nodo. Sus campos incluyen
6. * el número de nodo, el coste total del nodo, y su precedente. */
7. struct label {
8. int nro; /* numero del nodo */
9. int prev; /* nodo precedente (-1 para el nodo inicial )*/
10. int peso; /* peso o coste total de la trayectoria que
11. * conduce al nodo, i.e., el coste total desde
12. * el nodo inicial hasta el actual. Un valor
13. * de -1 denota el infinito */
14. int marca; /* si el nodo ha sido marcado o no */
15. };
16. typedef struct label label_t;
17.
18. using namespace std;
19.
20. void dijkstra( int, int **, int, int );
21.
22. int main () {
23.
24. /* cantidad total de nodos */
25. int numNodos = 8;
26.
27. /* Definiendo la matriz de adyacencia */
28. int i, j, **A;
29.
30. if ( ( A = new int*[numNodos] ) == NULL ) return 1;
31. for ( i = 0; i < numNodos; i++ )
32. if ( ( A[i] = new int[numNodos] ) == NULL ) return 1;
33.
34. for ( i = 0; i < 8; i++ )
35. for ( j = 0; j < 8; j++ )
36. A[i][j] = 0;
37.
38. /* por simplicidad, completamos solo los pares de nodos conectados */
39. A[0][1] = 16;
40. A[0][2] = 10;
41. A[0][3] = 5;
42.
43. A[1][0] = 16;
44. A[1][2] = 2;
45. A[1][5] = 4;
46. A[1][6] = 6;
47.
48. A[2][0] = 10;
49. A[2][1] = 2;
50. A[2][3] = 4;
51. A[2][4] = 10;
52. A[2][5] = 12;
53.
54. A[3][0] = 5;
55. A[3][2] = 4;
56. A[3][4] = 15;
57.
58. A[4][2] = 10;
59. A[4][3] = 15;
60. A[4][5] = 3;
61. A[4][7] = 5;
62.
63. A[5][1] = 4;
64. A[5][2] = 12;
65. A[5][4] = 3;
66. A[5][6] = 8;
67. A[5][7] = 16;
68.
69. A[6][1] = 6;
70. A[6][5] = 8;
71. A[6][7] = 7;
72.
73. A[7][4] = 5;
74. A[7][5] = 16;
75. A[7][6] = 7;
76.
77. /* Imprimir la matriz de adyacencia */
78. cout << "Matriz de adyacencia:" << endl << endl;
79. for ( i = 0; i < 8; i++ ) {
80. for ( j = 0; j < 8; j++ )
81. cout << setw(2) << A[i][j] << " ";
82. cout << endl;
83. }
84. cout << endl;
85.
86. /* calcular dijkstra a partir del nodo 0, a partir del nofo 7 */
87. dijkstra( numNodos, A, 0, 7 );
88.
89. /* liberar memoria */
90. delete [] A;
91. for ( i = 0; i < numNodos; i++ )
92. delete A[i];
93.
94. return 0;
95. }
96.
97. /* Calcula el coste minimo de alcanzar un nodo final 'b'
98. * grafo no dirigido con N nodos, a partir del nodo inicial 'a',
99. * dada su matriz de adyacencia A */
100. void dijkstra( int N, int **A, int a, int b ) {
101.
102. label_t *Labels;
103. int i, i0, j, peso;
104. int *ruta; /* array de nodos de la ruta minima */
105.
106. /* Crea din'amicamente el arreglo de etiquetas de nodo */
107. if ( ( Labels = new label_t[N] ) == NULL ) return;
108.
109. /* nodo inicial 'a' entre 0 y N - 1 */
110. if ( a < 0 || a > N - 1 ) return;
111. /* nodo final 'a' entre 0 y N - 1 */
112. if ( b < 0 || b > N - 1 ) return;
113.
114. /* inicializar las etiquetas de nodo */
115. for ( i = 0; i < N; i++ ) {
116. Labels[i].nro = i;
117. if ( i != a ) {
118. Labels[i].prev = -1; /* a'un no se ha definido predecesor */
119. Labels[i].peso = -1; /* infinito */
120. Labels[i].marca = 0;
121. }
122. else {
123. Labels[i].prev = -1; /* a'un no se ha definido predecesor */
124. Labels[i].peso = 0; /* coste del nodo inicial a s'i
mismo es cero */
125. Labels[i].marca = 0;
126. }
127. }
128.
129. /* continuamos este ciclo mientras existan nodos no marcados */
130. while ( 1 ) {
131. /* busca entre todos los nodos no marcados el de menor peso,
descartando los
132. * de peso infinito (-1) */
133. peso = -1;
134. i0 = -1;
135. for ( i = 0; i < N; i++ ) {
136. if ( Labels[i].marca == 0 && Labels[i].peso >= 0 )
137. if ( peso == -1 ) {
138. peso = Labels[i].peso;
139. i0 = i;
140. }
141. else if ( Labels[i].peso <= peso ) {
142. peso = Labels[i].peso;
143. i0 = i;
144. }
145. }
146. if ( i0 == -1 ) { /* termina si no encuentra */
147. cout << "Ya no quedan nodos por analizar." << endl;
148. break;
149. }
150.
151. cout << "*** Analizando nodo " << i0 << " ***" << endl;
152.
153. /* actualiza el peso de todos los sucesores (si los hay) del
nodo
154. * encontrado y luego se~nala dicho nodo como marcado */
155. for ( i = 0; i < N; i++ ) {
156. if ( A[i0][i] > 0 ) {
157. /* si el coste acumulado sumado al coste del enlace del
nodo i0 al nodo i
158. * es menor al coste del nodo i (o si el coste del nodo i es
infinito),
159. * debemos actualizar */
160. if ( Labels[i].peso == -1 || Labels[i0].peso + A[i0][i] <
Labels[i].peso ) {
161. if ( Labels[i0].peso + A[i0][i] < Labels[i].peso )
162. cout << " [ mejorando coste de nodo " << i << " ]" <<
endl;
163. Labels[i].peso = Labels[i0].peso + A[i0][i];
164. Labels[i].prev = i0;
165. cout << " coste de nodo " << i << " desde nodo " << i0
<< ": " << Labels[i].peso << endl;
166. }
167. }
168. }
169. Labels[i0].marca = 1;
170. cout << " [ nodo " << i0 << " marcado ]" << endl;
171.
172. /* para verificar, imprime los costes calculados hasta el
momento */
173. for ( i = 0; i < N; i++ ) {
174. cout << i << ": [";
175. if ( Labels[i].peso == -1 ) cout << "Inf";
176. else cout << Labels[i].peso;
177. cout << ", " << Labels[i].prev ;
178. if ( Labels[i].marca == 1 ) cout << ", x]" << endl;
179. else cout << "]" << endl;
180. }
181. cout << endl;
182.
183. /* pausa (opcional) */
184. cout << "presione ENTER para continuar ...";
185. cin.get();
186. }
187.
188. /* Ruta desde el nodo 'a' hasta el nodo 'b' */
189. int longitud = 2;
190. i = b;
191. while ( ( i = Labels[i].prev ) != a ) longitud++; /* primero
estimamos la longitud de la ruta */
192. if ( ( ruta = new int[longitud] ) == NULL ) return;
193.
194. ruta[longitud - 1] = b; /* luego rellenamos */
195. i = b;
196. j = 0;
197. for ( j = 1; j < longitud; j++ ) {
198. i = Labels[i].prev;
199. ruta[longitud - j - 1] = i;
200. }
201.
202. cout <<
"======================================================
==========" << endl;
203. cout << endl << "Ruta mas economica entre nodo " << a << " y
nodo " << b << ":" << endl << endl;
204. for ( i = 0; i < longitud; i++ ) {
205. cout << ruta[i];
206. if ( i < longitud - 1 ) cout << " - ";
207. }
208. cout << endl << endl << "Costo total: " << Labels[b].peso <<
endl << endl;
209.
210. delete ruta;
211. delete [] Labels;
212. }
4.3.4-Algoritmo de Euler
El origen de la teoría de los ciclos eulerianos fue planteado y resuelto por el
propio Leonhard Euler en 1736 en un problema que tiene el nombre de Siete
puentes de la ciudad de Königsberg (Prusia oriental en el siglo XVIII y
actualmente, Kaliningrado, provincia rusa) dando origen a la Teoría de los
grafos
El problema de los puentes de Königsberg, también llamado más
específicamente problema de los siete puentes de Königsberg, es un célebre
problema matemático, resuelto por Leonhard Euler en 1736 y cuya resolución
dio origen a la teoría de grafos. Su nombre se debe a Königsberg, el antiguo
nombre que recibía la ciudad rusa de Kaliningrado, que durante el siglo XVIII
formaba parte de Prusia Oriental, como uno de los ducados del Reino de
Prusia.

Esta ciudad es atravesada por el río Pregolya, el cual se bifurca para rodear
con sus brazos a la isla Kneiphof, dividiendo el terreno en cuatro regiones
distintas, las que entonces estaban unidas mediante siete puentes llamados
Puente del herrero, Puente conector, Puente verde, Puente del mercado,
Puente de madera, Puente alto y Puente de la miel.El problema fue formulado
en el siglo XVIII y consistía en encontrar un recorrido para cruzar a pie toda la
ciudad, pasando sólo una vez por cada uno de los puentes, y regresando al
mismo punto de inicio.

HISTORIA
Problema de los puentes de Königsberg
El problema, formulado originalmente de manera informal, consistía en
responder a la siguiente pregunta:

Dado el mapa de Königsberg, con el río Pregolya dividiendo el plano en cuatro


regiones distintas, que están unidas a través de los siete puentes, ¿es posible
dar un paseo comenzando desde cualquiera de estas regiones, pasando por
todos los puentes, recorriendo sólo una vez cada uno, y regresando al mismo
punto de partida?
La respuesta es negativa, es decir, no existe una ruta con estas características.
El problema puede resolverse aplicando un método de fuerza bruta, lo que
implica probar todos los posibles recorridos existentes. Sin embargo, Euler en
1736 en su publicación «Solutio problematis ad geometriam situs pertinentis»
demuestra una solución generalizada del problema, que puede aplicarse a
cualquier territorio en que ciertos accesos estén restringidos a ciertas
conexiones, tales como los puentes de Königsberg.

Análisis y solución del problema

Para dicha demostración, Euler recurre a una abstracción del mapa,


enfocándose exclusivamente en las regiones terrestres y las conexiones entre
ellas. Cada puente lo representó mediante una línea que unía a dos puntos,
cada uno de los cuales representaba una región diferente. Así el problema se
reduce a decidir si existe o no un camino que comience por uno de los puntos
azules, transite por todas las líneas una única vez, y regrese al mismo punto de
partida.

Análisis y solución del problema


Grafo Eulerianos
Un grafo es euleriano si contiene un camino o un circuito euleriano, Todo grafo
euleriano debe ser conexo.
4.3.5-Algoritmo de camino más corto
Es un algoritmo de búsqueda grafica que resuelve solo la fuente más corta de
un problema del camino para un gráfico con los negativos de bordes costos de
ruta, produciendo un camino más corto al árbol. Este algoritmo se utiliza a
menudo en la ruta y como una subrutina en otros algoritmos de grafos.
Para una fuente dada de vértice (nodo) en el gráfico, el algoritmo encuentra la
ruta con menor coste (es decir, el camino más corto) entre el vértice y cualquier
otro vértice.
El problema de la ruta más corta incluye un juego de nodos conectados donde
solo un nodo es considerado como el origen y solo un nodo es considerado
como el nodo destino. Su objetivo es determinar un camino de conexiones que
minimizan la distancia total del origen al destino. El problema se resuelve por el
“algoritmo de etiquetado”. La esencia del procedimiento es que analiza toda la
red a partir del origen; identifica de manera sucesiva la ruta más corta a cada
uno de los nodos en orden ascendente de sus distancias (más cortas), desde el
origen; el problema queda resuelto en el momento de llegar al nodo destino.
Definición del problema:
*- se tiene n nodos, partiendo del nodo inicial1 y terminando en el nodo final n.
*- arcos bi-direccionales conectan los nodos con distancias mayores que cero.
*- se desea encontrar la ruta de mínima distancia que conecta el nodo 1 con el
nodo n.
Por medio de la aplicación del algoritmo de este problema podemos conocer la
menor distancia entre un nodo origen y un nodo destino.
Pasos a seguir:
1. Elaborar un cuadro con todos los nodos y los ramales que salen de él.
2. Partiendo del origen, debemos encontrar el nodo más cercano a él.
3. Anular todos los ramales que entren al nodo más cercano elegido.
4. Comenzando en el origen se debe encontrar el nodo más cercano a él, por
intermedio del nodo ya elegido y volver al tercer paso hasta llegar al destino.
El algoritmo de Bellman-Ford:

Este algoritmo es una estructura básica muy parecido al algoritmo de Dijkstra,


pero en vez de seleccionar vorazmente el nodo de peso mínimo aun sin
procesar para relajarlo, simplemente relaja todas las aristas, y lo hace │V│-1
veces, siendo │V│ el número de vértices en el grafo. Las repeticiones permiten
a las distancias mínimas recorrer el árbol, ya que en la ausencia de ciclos
negativos, el camino más corto solo visita cada vértice una vez. A diferencia de
la solución voraz, la cual depende de la suposición de que los pasos sean
positivos, esta solución se aproxima más al caso general.

Existen dos versiones:


· Versión no optimatizada para grafos con ciclos negativos, cuyo coste de
tiempo es O(VE)
· Versión optimatizada para grafos con aristas de peso negativo, pero en
el grafo no existen ciclos de coste negativo, cuyo coste de tiempo, es también
O (VE).

Aplicaciones de encaminamiento:
Una variante distribuida al algoritmo del Bellman-Ford se usa en protocolos de
encaminamiento basados en vector de distancias, por ejemplo el protocolo de
encaminamiento de información (RIP). El algoritmo es distribuido porque
envuelve unas series de nodos (routers) dentro de un sistema autónomo (AS),
un conjunto de redes y dispositivos router IP administrados típicamente por un
Proveedor de Servicios de Internet (ISP). Se compone de los siguientes pasos:
1. Cada nodo calcula la distancia entre el mismo y todos los demás dentro de
un AS y almacena esta información en una tabla.
2. Cada nodo envía su tabla a todos los nodos vecinos.
3. Cuando un nodo recibe las tablas de distancias de sus vecinos, este
calcula la ruta más corta a los demás nodos y actualiza su tabla para reflejar
los cambios.
Las desventajas principales del algoritmo de Bellman-Ford en este ajuste son:
· No es escala bien.
· los cambios en la topología de red no se reflejan rápidamente ya que las
actualizaciones se distribuyen nodo por nodo.
· Contando hasta el infinito (si un fallo de enlace o nodo hace que un nodo
sea inalcanzable desde un conjunto de otros nodos, estos pueden estar
siempre aumentando gradualmente sus cálculos de distancia a él, y mientras
tanto puede haber bucles de enrutamiento).
Algoritmo de la Ruta más corta - Ejemplo
Un minero ha quedado atrapado en una mina, la entrada a la mina se
encuentra ubicada en el nodo 1, se conoce de antemano que el minero
permanece atrapado en el nodo 9, para llegar a dicho nodo hay que atravesar
una red de túneles que van conectados entre sí. El tiempo de vida que le queda
al minero sin recibir auxilio es cada vez menor y se hace indispensable hallar la
ruta de acceso al nodo 9 más corta. Las distancias entre nodos de la mina se
encuentran en la siguiente gráfica dadas en cientos de metros. Formule un
modelo de transbordo y resuelva mediante cualquier paquete de herramientas
de investigación operativa que permita establecer la ruta más corta para poder
así auxiliar al minero.

VARIABLES DE DECISIÓN
El nombre de las variables en este caso poco importa, dado que de ser
escogida para la solución básica eso significa simplemente que será empleada
como ruta para ir a rescatar al minero, sin embargo, nada tiene de malo el que
se le pueda asociar con el envío de unidades desde la entrada de la mina hacia
el minero, por ende, puede sugerirse este como nombre de las variables.
"Cantidad de unidades enviadas desde el nodo i hacia el nodo j".
X12 = Cantidad de unidades enviadas desde el nodo 1, hacia el nodo 2
X13 = Cantidad de unidades enviadas desde el nodo 1, hacia el nodo 3
X23 = Cantidad de unidades enviadas desde el nodo 2, hacia el nodo 3
X24 = Cantidad de unidades enviadas desde el nodo 2, hacia el nodo 4
X32 = Cantidad de unidades enviadas desde el nodo 3, hacia el nodo 2
X34 = Cantidad de unidades enviadas desde el nodo 3, hacia el nodo 4
X35 = Cantidad de unidades enviadas desde el nodo 3, hacia el nodo 5
X46 = Cantidad de unidades enviadas desde el nodo 4, hacia el nodo 6
X47 = Cantidad de unidades enviadas desde el nodo 4, hacia el nodo 7
X54 = Cantidad de unidades enviadas desde el nodo 5, hacia el nodo 4
X56 = Cantidad de unidades enviadas desde el nodo 5, hacia el nodo 6
X57 = Cantidad de unidades enviadas desde el nodo 5, hacia el nodo 7
X58 = Cantidad de unidades enviadas desde el nodo 5, hacia el nodo 8
X67 = Cantidad de unidades enviadas desde el nodo 6, hacia el nodo 7
X69 = Cantidad de unidades enviadas desde el nodo 6, hacia el nodo 9
X76 = Cantidad de unidades enviadas desde el nodo 7, hacia el nodo 6
X78 = Cantidad de unidades enviadas desde el nodo 7, hacia el nodo 8
X79 = Cantidad de unidades enviadas desde el nodo 7, hacia el nodo 9
X87 = Cantidad de unidades enviadas desde el nodo 8, hacia el nodo 7
X89 = Cantidad de unidades enviadas desde el nodo 8, hacia el nodo 9
Hay que recordar que el objetivo de este modelo es la consecución de un plan
de ruta que nos permita encontrar al minero lo más pronto posible al recorrer la
distancia mínima posible, por ende, la clave para plantear el modelo como si
fuese de transbordo es establecer una demanda y oferta igual a la unidad (1).

X12 + X13 = 1
X69 + X79 + X89 = 1

Restricciones de Balance

X12 + X32 - X23 - X24 = 0


X13 + X23 - X32 - X34 - X35 = 0
X24 + X34 + X54 - X46 - X47 = 0
X35 - X54 - X56 – X57 – X58 = 0
X46 + X56 + X57 - X67 – X69 = 0
X67 + X47 + X57 + X87 – X76 – X78 – X79 = 0
X78 + X58 – X89 = 0

En palabras sencillas: "Todo lo que entra a cada nodo es igual a lo que sale de
él"
FUNCIÓN OBJETIVO
ZMIN = 4X12 + 2X13 + 2X23 + 7X24 + 4X32 + 9X34 + 6X35 + 1X46 + 5X47 + 2X54 +
4X56 + 3X57+ 2X58 + 1X67 + 5X69 + 4X76 + 3X78 + 5X79 + 2X87 + 7X89
INGRESANDO LOS DATOS A WINQSB

SOLUCIÓN OBTENIDA MEDIANTE WINQSB


La ruta más corta para rescatar al minero tiene como distancia total 1600
metros (dado que las distancias estaban dadas en cientos de metros) y es tal
como se muestra en la siguiente gráfica.
Sin embargo, WinQSB cuenta con una metodología mucho más sencilla de
resolución de algoritmos de ruta más corta, metodología que explicaremos más
adelante, de todas formas, hemos encontrado cómo, aplicando debidamente la
razón y un algoritmo conocido como el de transbordo, podemos solucionar
problemas distintos en teoría.
4.3.6-Grafos conexos
Sea el grafo G=<V,A>, donde V es el conjunto de los nodos o vértices y A es la
colección de sus arcos o aristas en dependencia de si es un grafo orientado o
no orientado, se dice que G es conexo si y solo si entre cualquier par de
vértices x, y de V existe un camino que comience en x y termine en y.
En el caso de los grafos no orientados simples que entre cualquier par de
vértices existe una arista que los une se hacen llamar grafos completos.

Grafo conexo
Para hablar de grafo conexo, diremos que dos vértices u, v V(G) están
"conectados" si existe un camino (u, v) en G. Entonces, es fácil ver que la
conexión es una relación de equivalencia en V(G).

Para ver que la relación de ser conexo es simétrica, supongamos que u, v


pertenecen a V(G) y u distinto de v están conectados, entonces (por definición)
sabemos que existe un camino (u, v) que relaciona a u con v. Pero v está en
relación con u porque si existe un camino (u, v), también existe un camino (v,
u), (ver fig. 3.2 a)).

En forma trivial podemos considerar que el vértice u está conectado consigo


mismo; es decir, la relación de ser conexo también es reflexiva. Para ver que
es transitiva, consideramos que u y v están conectados y v y w también, con u
distinto de w. Entonces, existe el camino (u, v) y el camino (v, w). Por lo tanto,

existe el camino (u, w). (Ver fig. 3.2 b)).


Fig. 3.2
Sabemos que toda relación de equivalencia separa al conjunto V(G) en
subconjuntos no vacíos V1, V2, V3, ... , Vk, donde la relación es tal que dos
vértices u y v están conectados si y sólo si ambos, u y v, pertenecen al mismo
conjunto Vi. Por otra parte, los subgrafos G[V1], G[V2], ... , G[Vk] son
llamados componentes de G. Ahora, si G tiene exactamente una componente,
es decir, k = 1, el grafo G es conexo. Denotaremos por k(G)al número de
componentes conexas o simplemente componentes del grafo G.

Ejemplo 2: El grafo G de la fig. 3.3 tiene k(G) = 5.

Fig. 3.3
Observe que en la fig. 3.3 no existe (por ejemplo) un camino entre los vértices
de la componente G1 y los de la componente G4. Deducimos de aquí que
un grafo G es conexo si y sólo si todo par de vértices de G puede ser unido
por un camino.
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 (fuertemente) conexo
permite establecer una relación de equivalencia para sus vértices, la cual lleva
a una partición de estos en "componentes (fuertemente) conexos", 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.

4.3.7-Gráficas en computadora
Las computadoras se han convertido en una herramienta poderosa para
producir imágenes en forma rápida y económica. De hecho, no existe ninguna
área en que no se puedan aplicar las gráficas por computadora con algún
beneficio y, como consecuencia, no es sorprendente encontrar que se haya
generalizado tanto la utilización de las gráficas por computadora.
Los avances en la tecnología de la computación han hecho que las gráficas
interactivas por computadora sean una herramienta práctica. Hoy en día,
vemos que las gráficas por computadora se utilizan de manera rutinaria en
diversas áreas, como en la ciencia, ingeniería, empresas, industria, gobierno,
arte, entretenimiento, publicidad, educación, capacitación y presentaciones
gráficas.
Con pocas excepciones, los paquetes generales de gráficas están diseñados
para utilizarse con especificaciones de coordenadas cartesianas. Si los valores
de las coordenadas de una imagen se especifican en alguna otra estructura de
referencia (esférica, hiperbólica, etc.), es necesario convertirlos a coordenadas
cartesianas antes de poder capturarlos en el paquete de gráficas. Los paquetes
para propósitos especiales pueden permitir que se empleen otras estructuras
de coordenadas que son apropiadas para la aplicación. En general, se pueden
utilizar varias estructuras cartesianas de referencia distintas para crear y
desplegar una escena. Podemos construir la forma de objetos individuales,
como árboles o muebles, en una escena en estructuras de coordenadas de
referencia separadas, que se conocen como coordenadas de modelado, o a
veces como coordenadas locales o coordenadas maestras. Una vez que se
especifican las formas de objetos individuales, podemos colocar los objetos en
las posiciones adecuadas en la escena al utilizar una estructura de referencia
llamada coordenadas mundiales. Por último, la descripción de las coordenadas
mundiales a una o más estructuras de referencia de dispositivos de salida para
su despliegue. Estos sistemas de coordenadas de despliegue reciben el
nombre de coordenadas de dispositivo o coordenadas de pantalla en el caso
de un monitor de video. Las definiciones del modelado y de las coordenadas
mundiales nos permite establecer cualquier dimensión de punto flotante o de
entero conveniente sin obstáculos por las restricciones de un dispositivo de
salida particular.
Por lo general, un sistema de gráficas primero convierte las posiciones de
coordenadas mundiales a coordenadas de dispositivo normalizado, en el rango
de 0 a 1, antes de realizar la conversión final para especificar las coordenadas
de dispositivo.
Es posible describir una imagen de muchas maneras. Si supone que tenemos
un dispositivo de rastreo,una imagen se especifica por completo por el conjunto
de intensidades para las posiciones de pixel en el despliegue. En contraste, se
puede describir una imagen como un conjunto de objetos complejos, como
árboles y terreno o muebles y muros, colocados en posiciones de coordenadas
específicas en la escena. Las formas y los colores de los objetos se pueden
describir, a nivel interno, con matrices de pixel o con conjuntos de estructuras
geométricas básicas, como segmentos de línea recta y áreas de color de
polígonos. Entonces, la escena se despliega ya sea al cargar las matrices de
pixel en el búfer de estructura, o al convertir mediante rastreo las estructuras
geométricas básicas en patrones de pixel. Por lo regular, los paquetes de
programación de gráficas ofrecen funciones para describir una escena en
términos de estas estructuras geométricas básicas, que reciben el nombre de
primitivos de salida. y agrupar conjuntos de primitivos de salida en estructuras
más complejas. Cada primitivo de salida se especifica con los datos de las
coordenadas de entrada y otra información referente a la manera en que se
debe desplegar ese objeto. Los puntos y segmentos de línea recta son los
componentes geométricos más simples de las imágenes. Los primitivos de
salida adicionales que se pueden utilizar para crear una imagen incluyen
circunferencias y otras secciones cónicas, superficies cuadráticas, curvas y
superficies de "spline”, áreas de color de polígonos y cadenas de caracteres.
El trazo de líneas se efectúa mediante el cálculo de posiciones intermedias a lo
largo de la trayectoria de la línea entre dos posiciones extremas específicas.
Un dispositivo de salida se dirige para llenar estas posiciones entre los
extremos. Cuando se tienen dispositivos análogos, como un trazador vectorial
a base de pluma o un despliegue de rastreo aleatorio, se puede trazar una
línea recta de manera tenue de un extremo al otro. Se generan voltajes de
reflexión horizontal y vertical linealmente variables que son proporcionales a los
cambios requeridos en las direcciones de x y y para producir la línea tenue.
Los dispositivos digitales despliegan un segmento de línea recta al trazar
puntos discretos entre los dos extremos. Las posiciones de coordenadas
discretas a lo largo de la trayectoria de la línea se calculan a partir de la
ecuación de la línea. Para un despliegue de video de rastreo, el color
(intensidad) de la línea se carga entonces en el búfer de estructura, el
controlador de video "traza los pixeles" de la pantalla. Las posiciones en la
pantalla se expresan como valores enteros, de modo que las posiciones
trazadas sólo puedan aproximarse a las posiciones de la línea reales entre dos
extremos específicos.
Para los algoritmos a nivel de dispositivo de gráficas de rastreo que se verán
en los siguientes temas. las posiciones de objetos se especifican directamente
en coordenadas de dispositivo enteras.
Método Directo

Este método permite dibujar una línea, entre los puntos (xi,yi) y (xf,yf), utilizando la ecuación y = mx +
b generando a continuación la secuencia (xi+1 = xi + Δx, round (yi + 1)).
Con este cálculo se obtiene el píxel más cercano o sea aquel cuya distancia a la recta es menor.
Este método no es tan eficiente debido a que en cada iteración se requiere una multiplicación y una suma en
punto flotante, más la invocación del método de truncamiento.El uso de la función de redondeo y el hecho
de que para cada valor (discreto) de x se grafica un solo valor de y, ocasiona que la gráfica sea discontinua
para |m|>1.
Algoritmo DDA

El analizador diferencial digital (DDA; digital differential analyzer) es un algoritmo de línea de conversión de
rastreo que se basa en el cálculo ya sea de Δy, o de Δx, por medio de las siguientes ecuaciones:
Δy = m Δx
Δx = Δy/m
Se efectúa un muestreo de la línea e intervalos unitarios en una coordenada y se determinan los valores
enteros correspondientes más próximos a la trayectoria de la línea para la otra coordenada.
Se debe considerar primero una línea con pendiente positiva. Si la pendiente es menor o igual que 1, se
lleva a cabo un muestreo de x intervalos unitarios (Δx = 1) y se calcula cada valor sucesivo de y como
yk+1 = yk + m.
El subíndice k toma valores enteros a partir de 1 y aumenta a razón de 1 hasta que se alcance el valor final.
Ya que m puede ser cualquier número real entre 0 y 1, los valores calculados de y deben redondearse al
entero más cercano.
Para las líneas con una pendiente positiva mayor que 1, se revierten la funciones de x yde y. Es decir, se
realiza un muestreo de y en intervalos unitarios (Δy = 1 ) y se calcula cada valor sucesivo de x como
xk+1 = xk + 1/m.
Las ecuaciones anteriores se basan en la suposición de que las líneas deben procesarse del extremo
izquierdo al derecho. Si este procesamiento se revierte, de manera que sea el extremo derecho donde se
inicia, entonces se tiene ya sea Δx = -1 y
yk+1 = yk - m
o ( cuando la pendiente es mayor que 1 ) Δy - 1 con
xk+1 = xk - 1/m
Algoritmo de Punto Medio - Bresenham

Un algoritmo preciso y efectivo para la generación de líneas de rastreo, desarrollado por Bresenham,
convierte mediante rastreo las líneas al utilizar sólo cálculos incrementales con enteros que se pueden
adoptar para desplegar circunferencias y otras curvas.
El algoritmo de línea de Bresenham se basa en probar el signo de un parámetro entero, cuyo valor es
proporcional a la diferencia entre las separaciones de las dos posiciones de pixel de la trayectoria real de la
línea.
Para realizar el trazo de línea de Bresenham para una línea con una pendiente positiva menor que 1 se
deben seguir los siguientes pasos:
1.Se capturan los dos extremos de la línea y se almacena el extremo izquierdo en (x0 , y0).
2.Se carga (x0 , y0)en el búfer de estructura; es decir, se traza el primer punto.
3.Se calculan las constantes Δx , Δy, 2Δy y 2Δy-2Δx y se obtiene el valor inicial para el parámetro de
decisión como p0 = 2Δy - Δx.
4.En cada xk,a lo largo de la línea, que inicia en k=0, se efectúa la prueba siguiente: si pk <0, el siguiente
punto que se debe trazar es (xk +1, yk) y pk +1 = pk + 2Δy. De otro modo, el siguiente punto que se debe
trazar es (xk +1, yk+1) y pk +1 = pk + 2Δy - 2Δx
5.Se repite el paso 4 Δx veces.

Algoritmos para Generación de Círculos

Como la circunferencia es un componente que se utiliza con frecuencia en imágenes y gráficas, la mayor
parte de los paquetes de gráficas incluye un procedimiento para generar ya sea circunferencias completas o
arcos circulares. De modo más general se puede ofrecer un solo procedimiento para desplegar ya sea
curvas circulares o elípticas.

Una circunferencia se define como un conjunto de puntos que se encuentran, en su totalidad, a una
distancia determinada r de una posición central (xc , yc). Esta relación de distancia se expresa por medio del
teorema de Pitágoras en coordenadas cartesianas como

(x - xc)2 + (y - yc)2 = r2
La forma de la circunferencia es similar en cada cuadrante. Se puede generar la sección circular del
segundo cuadrante del plano de xy al notar que las dos secciones circulares son simétricas con respecto del
eje de las y. Y las secciones circulares del tercero y el cuarto cuadrantes se pueden obtener a partir de las
secciones del primero y el segundo cuadrantes al considerar la simetría en relación con el eje de
las x.También se puede decir que hay simetría entre octantes. Las secciones circulares en octantes
adyacentes dentro de un cuadrante son simétricas con respecto de la línea a 45° que divide los dos
octantes. Al aprovechar la simetría de la circunferencia de esta manera, se podrá generar todas las
posiciones de pixel alrededor de una circunferencia, calculando sólo puntos dentro del sector de x = 0 a x =
y.

Algoritmo de Punto Medio - Bresenham

Al igual que en el algoritmo de la línea de rastreo, en el algoritmo de punto medio se efectúa un muestreo en
intervalos unitarios y se determina la posición del pixel más cercano a la trayectoria específica de la
circunferencia en cada paso. Para un radio r determinado y una posición central en la pantalla (xc ,yc), se
puede establecer primero el algoritmo para calcular las posiciones de pixel alrededor de una trayectoria
circular centrada en el origen de coordenadas (0,0). Así, cada posición calculada (x , y) se mueve a su
posición propia en la pantalla al sumar xc,a x y yc a y. A lo largo de la sección circular de x =0 a x = y en el
primer cuadrante, la pendiente de la curva varía entre 0 y -1. Por tanto, se puede tomar pasos unitarios
en la dirección positiva de x en este octante y utilizar un parámetro de decisión para determinar cuál de las
dos posiciones posibles de y está más próxima a la trayectoria de la circunferencia en cada paso. Las
posiciones de los otros siete octantes se obtienen entonces por simetría.
Para aplicar el método punto medio, se debe definir una función de circunferencia como:
f circunferencia (x , y)= x2 + y2 - r2
f circunferencia (x , y)= 0. Si el punto estpa en el interior de la circunferencia, la función de la circunferencia es
negativa; y si está en su exterior, es positiva
Al igual que en el algoritmo para el trazo de líneas de Bresenham, el método del punto medio calcula las
posiciones de pixel a lo largo de una circunferencia utilizando adiciones y sustracciones de enteros, si
se supone que los parámetros de la circunferencia se especifican en coordenadas enteras de pantalla. Se
pueden resumir los pasos del algoritmo de la circunferencia de punto medio como sigue:
1. Se capturan el radio r y el centro de la circunferencia (xc , yc) y se obtiene el primer punto de una
circunferencia centrada en el origen como (x0 , y0) = (0 ,r).
2. Se calcula el valor inicial del parámetro de decisión como p0 = 5/4 - r.
3. En cada xk posición, al iniciar en k = 0, se realiza la prueba siguiente. Si pk < 0, el siguiente punto a lo
largo de la circunferencia centrada en (0 , 0) es (xk+1 , yk) y pk+1 = pk +2xk+1 +1. De otro modo, el siguiente
punto a lo largo de la circunferencia es (xk + 1, yk -1) y pk+1 = pk + 2xk+1 + 1 -2yk+1 donde 2xk+1 = 2xk +
2 y 2yk+1 = 2yk -2.
4. Se determinan puntos de simetría en los otros siete octantes.
5. Se mueve cada posición de pixel calculada (x , y) a la trayectoria circular centrada en (xc , yc)setrazan los
valores de las coordenadas: x = x + xc y= y + yc .
6. Se repiten los pasos 3 a 5 hasta que x ≥ y.
Algoritmo de Punto Medio - Bresenham

El planteamiento que utilizamos aquí es similar a aquel que empleamos en el despliegue de una
circunferencia de rastreo. Dados los parámetros rx, ry y (xc , yc), determinamos los puntos (x, y) para una
elipse en posición estándar centrada en el origen y luego alteramos los puntos, de modo que la elipse esté
centrada en (xc , yc).
El método de punto medio para elipse se aplica a lo largo del primer cuadrante en dos partes.
Definimos la ecuación de una elipse con (xc , yc) = (0,0) como
felipse(x, y) = r2yx2 + r2xy2 - r2xr2y
que tiene las propiedades siguientes:
felipse(x, y) < 0 si (x , y) está adentro de la frontera de la elipse
felipse(x, y) <=0 si (x , y) está en la frontera de la elipse
felipse(x, y) < 0 si (x , y) está afuera de la frontera de la elipse
Así, la función de la elipse felipse(x, y) sirve como un parámetro de decisión en el algoritmo de punto medio.
En cada posición del muestreo, seleccionamos el pixel siguiente a lo largo de la trayectoria de la elipse de
acuerdo con el signo de la función de la elipse evaluada en el punto medio entre los dos pixel candidatos.
En los pasos siguientes se presenta el algoritmo de la elipse de punto medio:
1.Se capturan rx , ry y el centro de la elipse (xc , yc ) y se obtiene el prime punto de una elipse centrada en el
origen como (x0 , y0 )= (0 , ry)
2.Se calcula el valor inicial del parámetro de decisión en la región 1 como p10 = r2y - r2x ry + (1 r2x )/4
3.En cada posición xk en la región 1, al iniciar en k=0 , se realiza la prueba siguiente. Si p1k <0, el punto
siguiente a lo largo de la elipse centrada en (0 ,0) es (xk+1 , yk) y p1k+1 = p1k +2 r2y xk+1 + r2y
De otro modo, el punto siguiente a lo largo del círculo es (xk +1 , yk - 1) y p1k+1 = p1k +2 r2y xk+1 - 2
r2xyk+1 +r2y con 2 r2y xk+1 = 2 r2y xk + 2 r2y , 2 r2x yk+1 = 2 r2x yk - 2 r2x
4.Se calcula el valor inicial del parámetro de decisión en la región 2 utilizando el último punto (x0 , y0)
calculado en la región 1 como p20 = r2y (x0 + 1/2)2 + r2x(y0 - 1)2 - r 2x r2y
5.En cada yk posición en la región 2, al iniciar en k = 0, se realiza la prueba siguiente. Si p2k >0, el punto
siguiente a lo largo de la elipse centrada en (0 ,0) es (xk , yk - 1) y p2k+1 = p2k - 2r2x yk+1 + r2x
De otro modo, el punto siguiente a lo largo del círculo es (xk +1 , yk - 1) y p2k+1 = p2k +2 r2y xk+1 - 2
r2xyk+1 +r2x utilizando los mismos cálculos incrementales para x y y que en la región 1.
6.Se determinan puntos de simetría en los otros tres cuadrantes.
7.Se mueve cada posición de pixel calculada (x , y) a la trayectoria elíptica centrada en (xc , yc)y se trazan
los valores de las coordenadas:
x = x + xc y = y + yc
8.Se repiten los pasos para la región 1 hasta que 2 r2y x>= 2 r2x y.
4.4-Análisis asintótico de las funciones
En Ciencias de la Computación se presenta con frecuencia la situación de
analizar dos o más algoritmos que resuelven el mismo problema para
determinar cuál de ellos requiere menos recursos.
Técnica derivada del análisis matemático de algoritmos basada en dos
conceptos fundamentales: la caracterización de datos de entrada y la
complejidad asintótica. Peor caso: Los casos de datos de entrada que
maximizan la cantidad de trabajo realizado por un algoritmo. Mejor caso: Los
casos de datos de entrada que minimizan la cantidad de trabajo realizado por
un algoritmo. Caso promedio: El valor medio de la cantidad de trabajo realizado
por un algoritmo. Se debe tener en cuenta la distribución probabilística de los
datos de entrada que se manejan.
4.4.1-Complejidad de los algoritmos
Definición: Sea Dn el conjunto de datos de entrada de tamaño n para un
algoritmo, y sea I ∈ Dn un elemento cualquiera. Sea t(I) la cantidad de trabajo
realizado por el algoritmo para procesar la entrada I. Se definen entonces las
siguientes funciones: Complejidad del peor caso: W(n) = max{t(I) : I ∈ Dn}
Complejidad del mejor caso: B(n) = min{t(I) : I ∈ Dn} Complejidad del caso
promedio: A(n) = P I∈Dn P r(I) ∗ t(I), donde P r(I) es la probabilidad de que
ocurra la entrada I
Denotamos el tamaño de instancia con n
Calculamos el número de operaciones y el consumo de memoria para varios
diferentes valores de n
Graficamos el desempeño del algoritmo en función de n
Buscamos a una función simple que nos dé una cota superior al
comportamiento observado.
Complejidad constante. Es la más deseada.
log n: Complejidad logarítmica. Esta complejidad suele aparecer en
determinados algoritmos con iteración o recursión no estructural.
n: Complejidad lineal. Es, en general, una complejidad buena y bastante usual.
Suele aparecer en la evaluación de ciclo principal simple cuando la complejidad
de las operaciones interiores es constante o en algoritmos de recursión normal.
n log n. También aparece en algoritmos con recursión no es estructurada y se
considera una complejidad buena.
n². Complejidad cuadrática: Aparece en ciclos o recursiones dobles.
n3. Complejidad cúbica: Aparece en ciclos o recursiones triples. Para un valor
grande de n empieza a crecer en exceso.
np: Complejidad polinómica (p∈N, p>3). Si p crece, la complejidad del algoritmo
es bastante mala.
c n: Complejidad exponencial. Debe evitarse en la medida de lo posible. Puede
aparecer en subprogramas recursivos que contengan dos o más llamadas
internas. En algoritmos donde aparece esta complejidad suele hablarse de
explosión combinatoria.
n!: Complejidad factorial. Es la complejidad más mala de todas, aunque
afortunadamente aparece con muy poca frecuencia.
Algunas de las funciones más comunes en el análisis de algoritmos, y en
particular de la notación O, son mostradas en la siguiente figura, la cual
presenta las funciones y sus respectivas gráficas.
4.4.2-Acotación de problemas
Énfasis en el peor caso, ya que representa una cota superior para la cantidad
de trabajo realizado por un algoritmo La idea fundamental consiste en tratar de
encontrar una función W(n), fácil de calcular y conocida, que acote
asintóticamente el orden de crecimiento de la función TA(n) Se estudia la
eficiencia asintótica de algoritmos: Cómo se incrementa la cantidad de trabajo
realizado por un algoritmo a medida que se incrementa (con valores
“suficientemente grandes”) el tamaño de la entrada Para realizar este
procedimiento se necesitan herramientas especiales: las notaciones asintóticas
Sea f una función de N en R, es decir, f: N→R. o(g) es el conjunto de las
funciones f, también de los naturales a los reales, tales que f = O(g)y o(g) ≠
Θ(g). En otras palabras o(f )= O(f )– Θ(f ) donde “– “ denota la diferencia de
conjuntos. De la expresión anterior se puede concluir que para toda función f
de los naturales en los reales, ninguna función g esta al mismo tiempo en Θ(f )
y en o(f ) En otras palabras Θ(f ) ∩ o(f )= ∅ Por lo regular o(g) se lee “o
pequeña de g”. Es fácil recordar que las funciones o(g) son las funciones “más
pequeñas” de O(g).
(g) es el conjunto de las funciones f, de los naturales a los reales, tales que.

Por lo regular ω (g) se lee “omega pequeña de g”. Sin embargo, ω (g) es muy
poco usada, probablemente porque es difícil recordar que las funciones en ω
(g) son las funciones más grandes de Ω (g).
4.4.3-Transformación de problemas
Definición 1 Sean f(n) y g(n) dos funciones [f,g : Z+ → R+ ]. Se dice que f(n) es
“O grande” de g(n) y se escribe como: f(n) = O(g(n)) Si existen dos constantes
positivas c, n0 tal que f(n) ≤ c g(n) para todo n≥ n0
Ejemplo 1 Sea f(n)=n3 + 20 n2 + 100 n f(n) = O(g(n)) Puesto que: n 3 + 20 n2 +
100 n <= n3 + 20 n3 + 100 n3 = 121 n3 Escogiendo C = 121 y n 0 = 0 Es
suficiente para que se cumpla la definición.
Otro ejemplo, sea f(n)=lg n, y g(n)=(n/4)2 a b g () f () g () <=f () g ()>f ()
Podemos observar que: g(n) < f(n) para todo n, ¿tal que a < n < b. Será que
g(n) = O(f(n)) ?, la respuesta es NO pues si hacemos n0=a, la igualdad anterior
no se cumple para n > b, y por lo tanto no se cumple para todo n > n 0. ¿Será
que f(n) = O(g(n)) ?, la respuesta es SI pues si hacemos n0=b, la igualdad
anterior si se cumple para n > b, y por lo tanto se cumple para todo n > n0. Lo
anterior considerando por ejemplo c=1.

Definición 2 Sean f(n) y g(n) dos funciones [f,g : Z+ → R+ ]. Se dice que f(n) es
“Omega grande” de g(n) y se escribe como: f(n) = Ω(g(n)) Si existen dos
constantes positivas c y n0 tal que f(n) ≥ c g(n) para todo n≥ n0
Ejemplo, sea f(n)=lg n, y g(n)=(n/4)2
¿Será que f(n) = Ω(g(n)) ?, la respuesta es NO porque no se puede encontrar c
y n0 que satisfaga f(n) que satisfaga f(n) ≥ cg(n) para todo n ≥ n 0. ¿Será que
g(n) = Ω(f(n)) ?, la respuesta es SI pues si hacemos n0=b, la igualdad anterior si
se cumple para c=1 y n ≥ b, y por lo tanto se cumple para todo n > n0.
4.4.4-Cotas superiores e inferiores
Definición 3 (Cota Asintótica) Sean f(n) y g(n) dos funciones [f,g : Z+ → R+ ].
Se dice que f(n) es “Theta grande” de g(n) y se escribe como: f(n) = Θ(g(n)) Si
existen dos constantes positivas c1 c2, y n0 tal que c2 g(n) ≤ f(n) ≤ c2 g(n) para
todo n≥ n0
Las cotas de complejidad nos ayudan a clasificar los algoritmos de acuerdo a
su tiempo de ejecución. Si tenemos un algoritmo que para una
entrada n tarda f(n) en ejecutarse, lo que nos interesa es encontrar una cota
que crezca de manera similar a f(n).

Notación O (Omicrón, cota superior)

Dada una función f, queremos estudiar aquellas funciones g que a lo sumo


crezcan tan deprisa como f. Lo que buscamos es que para una función g(n),
encontrar una función asintótica que cumpla:
O(g(n)) = {f(n): con constantes positivas c y n0 tales que 0 ≤ f(n) ≤ cg(n), para
toda n > n0.}

En términos más sencillos, si tenemos una función O(n2), quiere decir que el
tiempo de ejecución f(n) nunca será mayor a cn2. Como estamos acotando por
la parte superior, si un algoritmo cumple O(n2), también cumple O(n3) (o
cualquier otra función de orden mayor), ya que si f(n) ≤ cn2 con mayor
razón f(n) ≤ cn3.

Normalmente estamos interesados en la función de menor crecimiento que


satisfaga la cota y es común asumir que, en efecto, es la menor que cumple.
Nosotros lo haremos a lo largo de este libro, a menos que se especifique lo
contrario.

La notación O se utiliza la mayoría de las veces para acotar el peor caso de un


algoritmo, ya que implica que cualquier otra entrada correrá en un tiempo
menor o igual que el especificado por la función.

Algo interesante de esta notación es que en algoritmos iterativos se puede


calcular fácilmente contando el mayor número de ciclos anidados de la
implementación (aunque no garantiza que sea la menor función).

Existe una notación alterna muy similar, que es la notación o (omicrón


minúscula). La diferencia es que debe cumplir que 0 ≤ f(n) < cg(n). Es decir,
para un algoritmo con tiempo de ejecución n2 se cumple O(n2) pero no o(n2).
Esta notación indica que cuando las dos funciones tienden a infinito, f(n) se
vuelve insignificante en comparación a g(n). Es muy inusual que se use esta
notación.

Notación Ω (Omega, cota inferior)

Dada una función f, queremos estudiar aquellas funciones g que a los sumo
crecen tan lentamente como f. Esta cota define que, para una función g(n),
tenemos una función asintótica la cual cumple:
Ω(g(n)) = {f(n): con constantes positivas c y n0 tales que 0 ≤ cg(n) ≤ f(n),
toda n > n0.}

Esto significa que para valores más grandes que n0, los resultados de f(n) no
pueden ser menores a Ω(n).

Como esta notación especifica un límite inferior, es común que se utilice para
acotar el caso de mejor tiempo de ejecución, ya que cualquier otro caso tiene
que ser forzosamente mayor a éste.

Al igual que con la cota superior, la cota inferior que es más útil es aquella que
se aproxima en mayor grado a f sin sobrepasarla, y estará implícito dentro del
libro. También de manera análoga a O, existe una notación similar a Ω que es
ω (omega minúscula). En esta cota se cumple que 0 ≤ cg(n) < f(n), y tampoco
es utiliza comúnmente.
Esta notación se acostumbra utilizarla para acotar el tiempo medio de ejecución
de un algoritmo.
Figura 2.1: Representación gráfica de la notación Θ

Fuentes
http://teoriasdelosalgoritmo.blogspot.mx/2013/02/algoritmo-de-euclide-grupo-
10.html
http://teoriasdelosalgoritmo.blogspot.mx/2013/02/algoritmo-para-la-ruta-mas-
corta-grupo-8.html
https://www.ingenieriaindustrialonline.com/herramientas-para-el-ingeniero-
industrial/investigacion-de-operaciones/algoritmo-de-la-ruta-mas-corta/
https://www.ingenieriaindustrialonline.com/herramientas-para-el-ingeniero-
industrial/investigacion-de-operaciones/algoritmo-de-dijkstra/
http://cidecame.uaeh.edu.mx/lcc/mapa/PROYECTO/libro23/unidad_1__grficas_
por_computadora.html
https://www.ecured.cu/Grafo_conexo
https://sites.google.com/site/algoritmosyestructuras2/unidad-iii-fundamentos-de-
analisis-asintomatico-de-algoritmos/analisisasintotico.pdf?attredirects=0&d=1
http://pier.guillen.com.mx/algorithms/02-analisis/02.4-cotas.htm
http://teoriadegrafos.blogspot.mx/2007/03/grafos-conexos.html
https://es.wikibooks.org/wiki/Matem%C3%A1tica_Discreta/Teor%C3%ADa_de_
grafos#Grafos_conexos
https://prezi.com/hhym8cdy9_xf/grafo-de-euler-algoritmo/