Este documento trata sobre la complejidad de algoritmos y el análisis asintótico. Explica que la complejidad mide el uso de recursos como tiempo, memoria o almacenamiento. Luego describe cómo determinar el tiempo lógico de ejecución de un algoritmo ignorando detalles pequeños y contando los pasos. Finalmente, introduce la notación Big-O para clasificar algoritmos como O(1), O(n), O(n2), etc. dependiendo de cómo escala su tiempo de ejecución con el tamaño de la entrada.
2. Introducción
Complejidad es la medida del uso de
algún recurso por parte de un algoritmo.
Estos recursos pueden ser requerimientos
de almacenamiento, memoria o tiempo.
Mtl Lourdes Cahuich 2
3. Tiempo de un algoritmo
Por lo general, la cantidad de tiempo que
un algoritmo toma para completarse es el
recurso más frecuentemente encontrado
en el contexto de complejidad
Mtl Lourdes Cahuich 3
4. Análisis Asintótico
El análisis asintótico es la determinación
de la cantidad de recursos usados por un
algoritmo.
Usualmente, este recurso es el tiempo de
ejecución del algoritmo.
El tiempo de ejecución de un programa es
sobre todo una cantidad física.
Mtl Lourdes Cahuich 4
5. Análisis asintótico
En principio, podemos detenernos a mirar
y ver cuánto tiempo toma el programa
para ejecutarse.
De hecho, todos los sistemas
computacionales soportan un reloj interno,
y uno puede medir el tiempo de ejecución
aún sin la ayuda de un dispositivo externo
para hacerlo
Mtl Lourdes Cahuich 5
6. Tiempo de ejecución
El primer aspecto a notar es que el tiempo
de ejecución no es una cantidad fija, al
contrario, el tiempo de ejecución en casi
todos los programas depende de la
entrada específica proporcionada al
programa.
Mtl Lourdes Cahuich 6
7. Tiempo de ejecución
Los tiempos de ejecución pueden diferir
drásticamente.
Sin embargo, se acostumbra referir el
tiempo de ejecución de un programa como
una entrada de tamaño n, significando el
peor tiempo posible de ejecución para
todas esas entradas.
Mtl Lourdes Cahuich 7
8. Complejidad en el peor caso
Esta medida es llamada complejidad del
peor caso
De forma alternativa, podemos analizar el
promedio de tiempo que toma procesar
una entrada de cierto tamaño, lo que es
llamado complejidad del caso promedio.
Mtl Lourdes Cahuich 8
10. Problemas para medir el tiempo
Otro problema al medir el tiempo de
ejecución es que éste obviamente
depende mucho de la máquina usada
para ejecutar el programa.
También, depende del compilador usado,
el nivel de optimización, la carga de la
computadora, etc
Mtl Lourdes Cahuich 10
11. Tiempo lógico de ejecución
Para evitar estas complicaciones,
podemos usar una medida diferente:
tiempo de ejecución lógico, el cual es el
número de pasos en la ejecución del
algoritmo.
Por quot;un pasoquot; nos referimos
esencialmente a un ciclo de CPU
Mtl Lourdes Cahuich 11
12. Tiempo lógico de ejecución
El contar el número de pasos nos permite
tener una medida útil del tiempo de
ejecución.
Sin embargo, si un algoritmo va a ser
ejecutado en una computadora específica,
es todavía necesario desarrollar medidas
del tiempo de ejecución en unas cuantos
casos.
Mtl Lourdes Cahuich 12
13. ¿Cómo Determinar el Tiempo de
Ejecución Lógico?
Es importante ignorar los pequeños
detalles y ver el comportamiento a gran-
escala
Usemos un ejemplo para explicar cómo
determinar el tiempo lógico de ejecución
Mtl Lourdes Cahuich 13
14. Determinar el tiempo lógico
Considera el algoritmo de ordenación por
selección.
De este programa, establecer las
variables locales toma un número
pequeño y constante de pasos
Mtl Lourdes Cahuich 14
15. Determinar el tiempo lógico
Si recuerdan el algoritmo de ordenamiento
por selección, cada elemento se
comparaba contra todos los demás y en
cada paso se ubica un elemento en la
posición correspondiente
Mtl Lourdes Cahuich 15
16. Determinar el tiempo lógico
El cuerpo del bucle externo es ejecutado
v.size() - 1 veces, donde v.size()
es el número de elementos vector
Definiremos a n como el número de
elementos en el vector
Mtl Lourdes Cahuich 16
17. Determinar el tiempo lógico
Durante su primera ejecución, el bucle
interno itera (n - 1) veces.
Durante su segunda ejecución (cuando i
iguala a 1), el bucle interno itera (n - 2)
veces.
Mtl Lourdes Cahuich 17
18. Determinar el tiempo lógico
El cuerpo de este bucle interno contiene
un estatuto-if.
Este estatuto puede ser ejecutado en una
cantidad constante de tiempo.
Mtl Lourdes Cahuich 18
19. Determinar el tiempo lógico
Por lo tanto, tenemos la siguiente suma
que representa el número de operaciones
desarrolladas usando ordenación por
selección.
Mtl Lourdes Cahuich 19
20. Diferencias en los tiempos de los
algoritmos
Cada término de esta suma representa el
número de pasos desarrollados por cada
iteración del bucle externo.
(n - 1) + (n - 2) + (n - 3) + ... + 3 + 2 + 1
Mtl Lourdes Cahuich 20
21. Diferencias en los tiempos de los
algoritmos
La suma de esta expresión iguala a (n2 -
n) / 2.
Resumiremos este resultado diciendo que
la ordenación por selección es un
algoritmo cuadrático.
Mtl Lourdes Cahuich 21
22. Diferencias en los tiempos de los
algoritmos
Observa que cuadrático significa que al
duplicar el tamaño de la entrada resultará
en un incremento cuádruple en el tiempo
de ejecución
Mtl Lourdes Cahuich 22
23. Diferencias en los tiempos de los
algoritmos
Una entrada diez veces más grande
tomará cien veces más en procesarse.
En otras palabras, los algoritmos
cuadráticos no escalan muy bien las
entradas grandes.
Mtl Lourdes Cahuich 23
24. Diferencias en los tiempos de los
algoritmos
Lo opuesto a los algoritmos cuadráticos
son los algoritmos logarítmicos.
En los algoritmos logarítmicos, el número
de pasos requeridos para solucionar un
problema no se incrementa de manera
significativa cuando el número de
entradas se incrementa dramáticamente.
Mtl Lourdes Cahuich 24
25. Diferencias en los tiempos de los
algoritmos
Un ejemplo de algoritmo logarítmico es
una búsqueda binaria.
En una búsqueda binaria, el número de
elementos (la entrada) en donde se
realiza la búsqueda se reduce a la mitad
en cada pasada.
Mtl Lourdes Cahuich 25
26. Diferencias en los tiempos de los
algoritmos
Esto permite que la búsqueda binaria
trabaje muy bien aún en conjuntos de
datos muy grandes.
Mtl Lourdes Cahuich 26
27. Notación Big-Oh
La notación Big-Oh ofrece una buena
opción para comparar el tiempo de
ejecución de los algoritmos.
En un análisis asintótico, usamos la
notación Big-Oh para referirnos al orden
más alto, o término más dominante del
análisis del tiempo de ejecución de una
función
Mtl Lourdes Cahuich 27
28. Big Oh (orden)
La propiedad más importante de la
notación Big-Oh es que nos permite
ignorar términos y constantes de orden
bajo, como en el siguiente ejemplo.
500n3 + 10n2 + 17n + 121345 = O(n3)
Mtl Lourdes Cahuich 28
29. Diferencias en los tiempos de los
algoritmos
En el ejemplo anterior, el término de orden
más alto en la ecuación del tiempo de
ejecución es 500n3.
Como lo hicimos con los términos de
orden bajo, también podemos ignorar la
constante en este término y establecer
que el tiempo de ejecución es O(n3).
Mtl Lourdes Cahuich 29
30. Diferencias en los tiempos de los
algoritmos
La mayoría de los algoritmos que encontramos
caen en alguna de las siguientes clases.
O(1) – tiempo constante
O(log(n)) – tiempo logarítmico
O(n) – tiempo lineal
O(n log(n)) – tiempo quot;n-log-nquot;
O(n2) – tiempo cuadrático
O(n3) – tiempo cúbico
O(2n) – tiempo exponencial
Mtl Lourdes Cahuich 30
31. Diferencias en los tiempos de los
algoritmos
Los algoritmos que tiene un tiempo de
ejecución arriba de O( n log(n)) escalan
muy bien a instancias de problemas
grandes.
Mtl Lourdes Cahuich 31
32. Diferencias en los tiempos de los
algoritmos
Los algoritmos cuadráticos y más aún los
algoritmos cúbicos, muestran un
desempeño muy degradado cuando la
entrada es grande.
Por último, los algoritmos exponenciales
pueden ser usados solamente con
entradas muy pequeñas
Mtl Lourdes Cahuich 32
33. Diferencias en los tiempos de los
algoritmos
Es importante expresar el tiempo de
ejecución lógico en términos del tiempo
físico, usando unidades familiares como
segundos, minutos, horas, etc., para tener
una idea de lo que estas caracterizaciones
realmente significan.
Mtl Lourdes Cahuich 33
34. Primero, consideremos un algoritmo O(n
log(n)) que toma 1 segundo en una
entrada de tamaño 1000
tamaño de la entrada tiempo de ejecución
1000 1 segundo
2000 2.2 segundos
5000 6.2 segundos
10000 13.3 segundos
100000 2.77 minutos
106 33.3 minutos
107 6.48 horas
Mtl Lourdes Cahuich 34
35. En comparación, aquí se muestra un
algoritmo cuadrático que también toma 1
segundo en una entrada de tamaño 1000.
tamaño de la entrada tiempo de ejecución
1000 1 segundo
2000 4 segundos
5000 25 segundos
10000 1.66 minutos
100000 2.77 horas
106 11.5 días
107 3.25 años
Mtl Lourdes Cahuich 35
36. Imagina que un algoritmo exponencial
toma un segundo en una entrada de
tamaño 10.
Podemos esperar aproximadamente los
siguientes tiempos de ejecución para
instancias más grandes
Mtl Lourdes Cahuich 36
37. Ejecución exponencial
tamaño de la entrada tiempo de ejecución
10 1 segundo
15 32 segundos
20 17.1 minutos
25 9.1 horas
30 12.1 días
35 1.09 años
40 34.9 años
50 35700 años
100 4.02 1019 años
Mtl Lourdes Cahuich 37
38. Desafortunadamente, existen algunos
problemas computacionales famosos que
parecen requerir tiempos de ejecución
exponencial
Encontrar si un circuito booleano con n
entradas puede producir siempre
quot;verdaderoquot; como resultado es uno de
ellos: existen 2n combinaciones posibles
de entrada, y no se conocen atajos para
simplificar el cálculo.
Mtl Lourdes Cahuich 38
40. Determinar el tiempo en cada
algoritmo
cualquier secuencia de estatutos básicos,
como 1)tareas de tipo incorporado ó 2)
operaciones de tipo incorporado (suma,
multiplicación, comparación, incremento,
decremento, operaciones booleanas, etc.),
toman tiempo constante (O(1)).
Mtl Lourdes Cahuich 40
41. Determinar el tiempo en cada
algoritmo
Las llamadas a la función tienen que ser
contabilizadas por separado.
Primero, está el costo de organizar la
llamada de la función.
Si sólo se usan parámetros de referencia,
este costo es O(1).
Mtl Lourdes Cahuich 41
42. Determinar el tiempo en cada
algoritmo
Pero, si se invoca-por-valor, tenemos que
contar el costo de copiar la entrada.
El mismo comentario aplica para regresar
el resultado del cálculo.
Y, por supuesto, tenemos que contar la
evaluación del cuerpo de la función
Mtl Lourdes Cahuich 42
43. Determinar el tiempo en cada
algoritmo
Un bucle-for toma el tiempo t(0) + t(1) + ...
+ t(n-1) donde t(k) es el tiempo requerido
por el cuerpo del bucle cuando i iguala a
k, en otras palabras, el tiempo requerido
para completar la iteración k del bucle-for
for (int i = 0; i < n; ++i)
{
cuerpo
}
Mtl Lourdes Cahuich 43
44. Determinar el tiempo en cada
algoritmo
Si el cuerpo se toma el tiempo O(1), el
tiempo del bucle resulta en O(n).
Pero, si el cuerpo se toma el tiempo O(i),
obtenemos O(n2) para el bucle completo.
Por último, si el cuerpo se toma el tiempo
O(i2) obtenemos O(n3) para el bucle
completo
Mtl Lourdes Cahuich 44
45. Determinar el tiempo en cada
algoritmo
Otras estructuras de bucle pueden ser
manejadas de la misma forma.
Agrega el costo de cada ejecución
individual del cuerpo del bucle, tomando
en cuenta que este costo puede depender
de los valores de algunos parámetros del
bucle.
Mtl Lourdes Cahuich 45