Está en la página 1de 66

Analizando algoritmos

• El análisis de un algoritmo es para predecir los recursos que este


requiere.

• Recursos como:
– Tiempo (complexity time).

– Memoria (complexity space).

– Comunicación (complexity communication).


• Lo mas frecuente es la complejidad de tiempo (complexity time).

• Generalmente, para un problema se analizan diversos algoritmos


candidatos, esto permite identificar al mas eficiente.

• El análisis puede indicar mas de un candidato viable, pero en el


proceso diversos algoritmos generalmente son descartados.
• Analizar un algoritmo simple puede ser un descubrimiento. Pues
− Como el comportamiento de un algoritmo puede ser diferente para cada
entrada posible, entonces es necesario resumir el comportamiento en
fórmulas fácilmente entendibles.

− Aunque, generalmente, se selecciona un solo modelo de máquina (para


analizar un algoritmo); es posible enfrentarse a muchas opciones (decidir
cómo expresar nuestro análisis).

¿Que
modelo
debo
usar?
• En el proceso de análisis se debe:
− Hallar un medio de expresión que sea simple para escribir y manipular,

− Mostrar las características importantes de los recursos que el algoritmo


requiere, y

− Suprimir detalles tediosos.

• Para el análisis, la matemática requerida puede incluir:


– Matemática discreta.

– Teoría de probabilidades.

– Algebra.

– Habilidad para identificar los términos mas significantes en una fórmula.


Problemas y algoritmos

 Un algoritmo es un método detallado paso a paso para la solución


de un problema.

 ¿Qué es un problema?.

 Los siguientes cuatro primeros problemas han motivado el


desarrollo de la teoría de complejidad.
Problema 1: Grafos alcanzables (graph reachability)

 Grafo: G = (V, E).

 Muchos problemas computacionales están relacionados


con grafos.

 Problema básico:
– Dado un grafo G y dos nodos 1, n  V, ¿existe un camino de 1 hacia n?.
Este problema es llamado alcanzabilidad.
 La alcanzabilidad tiene un conjunto infinito de posibles instancias.
– Cada instancia es un objeto matemático del cual damos una interrogante y
se espera una respuesta.
 Ejemplo:
– El grafo y dos de sus nodos.

 El tipo de pregunta formulada es lo que caracteriza el problema.

 Nota: La alcanzabilidad formulada tendrá como respuesta un SI o un


NO.

 Por lo tanto, tales problemas son llamados: Problemas de decisión.


 Estamos interesados en algoritmos que solucionan problemas.
Pero es necesario conocer un modelo formal (MT) para expresar
cualquier algoritmo.

 El problema de la alcanzabilidad puede ser solucionado con un


algoritmo de busca (search algorithm).

– Complejidad de tiempo: O(n2).


– Complejidad de espacio: O(n).
Problema 2: Flujo máximo (maximum flow)

 Es una generalización de la alcanzabilidad en grafos.

 Se trabaja con un tipo especial de grafo, llamado: Red.

Capacidad del
arco: c(i,j)

Nodo de origen Nodo de destino

Flujo del arco (i,j): f(i,j)


N = (N, A, s, t, c)
 (i,j), f(i,j)  c(i,j)
 Problema: Dada un red N, hallar un flujo de mayor valor posible.

 Este problema no es un problema de decisión, pero puede ser


convertido, es decir:
– Dada una red y un valor entero k, ¿existe un flujo de valor k o mas ?.

 Existes diversos algoritmos:


– Los algoritmos de tiempo polinomial se basan en el llamado “teorema de
flujo maximo-corte mínimo”.
 Algunos algoritmos presentan:
– Complejidad de tiempo:
 O(n3C), donde C denota la capacidad máxima de cada arco en la red.

 O(n5).

– Complejidad de espacio:
 O(n2).
Problema 3: casamiento bipartido (bipartite matching)

 Un grafo bipartido se define por:


– B = (U, V, E)
 Donde
– U = {u1, u2, …, un}: Hombres.

– V = {v1, v2, …, vn}: Mujeres.

– EUxV : Conjunto de arcos.

U V
 Problema computacional
– Dado un grafo bipartido, ¿ existe un casamiento?.
 Un concepto central en los algoritmos es la llamada: Reducción.
– Una reducción es un algoritmo que soluciona un problema A,
transformando cualquier instancia de A en otra que sea equivalente de un
problema B, del cual se conoce como se solucionan sus instancias.

Red con los:


Grafo bipartido: nodos: U  V  {s, t}.
(U, V, E)
arcos: {(s, u)/ u  U}  E  {(v, t)/ v V}

Reducción

U V
 Formalmente:
– Un grafo bipartido original tiene un casamiento sii la red resultante tiene
un flujo de valor n.

 Matching max flow.

 Con esta reducción se afirma que el problema de casamiento es


polinomial.

Me he casado
polinomialment
e
Problema 4: Agente viajero (TSP)

 Problema:
– Dada n ciudades y un distancia dij entre dos ciudades cualquiera i y j. El
problema es cubrir todas las ciudades al menor costo.

 Problema de decisión para el TSP:


– Dado un limite B y la matriz dij que denota distancias entre las ciudades,
¿existe un tour con una distancia total de a lo mas B?.
 Procedimiento:
– Enumerar todas la posibles soluciones, computando el costo que ella
implica y escogiendo la mejor.

 Análisis de eficiencia computacional:


– Complejidad de tiempo:
 n!, pues existen ½(n-1)! tours que se deben considerar.

– Complejidad de espacio:
 O(n), pues solamente se necesita recordar la permutación actualmente
examinada.

 No existe un algoritmo polinomial que solucione el problema. Pero


existen heurísticas.
Problema abierto en ciencia de la
computación:

¿P = NP?
Modelamiento matemático

 Metodología
– Conjunto de conceptos que cohesionan los principios y técnicas,
mostrando cuando, como y porque usarlos en situaciones diferentes.

 La metodología que usa la matemática en la solución de problemas,


es llamada modelamiento matemático.

Problema Solución

Modelo abstracto Modelo transformado


 Ejemplo
– Supongamos una red trenes entre las ciudades A, …, F

Problema: Hallar un subconjunto de la red de modo que enlace todas


las ciudades y tenga una longitud mínima.

B C D E F
A 5 10
B 5 10 20
C 20 30
D 20
E 10

Esta tabla es un modelo del mundo real.


20

5 5
A B C
Esta tabla puede ser transformada en
10 10 20 30
un grafo (modelo matemático)
20 10
D E F
El siguiente paso es:

Hallar una solución en términos de este modelo, es decir, hallar una


grafo G’ con la misma cantidad de nodos y un conjunto mínimo de arcos
que conecte todas las ciudades y que satisfaga la condición del
problema.

El modelo matemático es escogido, con el objetivo de obtener una solución.

La solución es dada por un algoritmo:

1) Seleccionar cualquier nodo y colocarlo en el conjunto de nodos ya


conectados.
2) Escoger, dentro de los nodos no conectados, aquel que este mas cerca de
un nodo conectado. Si existe mas de un nodo con esa característica,
escoger arbitrariamente cualquiera de ellos.
3) Repetir el paso 2 hasta que todos los nodos estén conectados.
Por lo tanto, dado la red:
20

5 5
A B C

10 10 20 30

20 10
D E F

Tenemos las siguientes soluciones:

5 5 5 5
A 5 A 5 A B C
B C B C

10 20 10 20 10

20 10
10 F 10 F D E F
D E D E

5 5
A B C

10

20 10
D E F
En resumen:

¿Qué hemos hecho?


1) Obtener un modelo matemático para el problema.

2) Formulación de un algoritmo en términos del modelo, es decir es una


técnica de resolución de problemas en Ciencia de la Computación.
Análisis de algoritmos
Introducción

• Antes de analizar un algoritmo, primero se debe tener claro:


1) Modelo computacional.

2) Dominación asintótica.

3) Análisis de algoritmos.
– Costo de un algoritmo.
– Función de complejidad de un algoritmo.
– Tamaño de la entrada de los datos.
– Clases de comportamiento asintótico.
– Jerarquía de las funciones.
– Casos de complejidad.
– Comparación de programas.
– Técnicas para el análisis de algoritmos.
1) Modelo computacional

• Aquel que incluye los recursos que serán usados, además de los
costos que implica su uso.
• RAM
¿Que
modelo
• PRAM debo
usar?
• Modelo RAM:
– Las instrucciones son ejecutadas una después de la otra, con operaciones
no concurrentes.

X1 X2 ... Xn Cinta de entrada

Acumulador

Contador Programa

.
.
.
Memoria

Cinta de salida y1 y2 ... yn


• Modelo PRAM:
– Investigar modelos para computadores paralelos.

Unidad de Control

P1 P2 Pp
... ... ...
...
Memoria Privada Memoria Privada Memoria Privada

Interconexión en Red

...
Memoria Global
Memoria distribuida:

Memorias Procesadores

.
. Interconnection
. network
p-1

.
I/O paralela .
.
2) Dominación asintótica

 El análisis de un algoritmo cuenta con algunas operaciones


elementales.

 La medida de costo o medida de complejidad indica el crecimiento


asintótico de la operación considerada.
 Definición: Una función f(n) domina asintóticamente a otra función g(n), si
existen las constantes c, m  0 tal que, para n  m, se tiene
|g(n)|  c.|f(n)|

– Sean g(n) = (n+1)2 y f(n) = n2 . Entonces:


 g(n) y f(n) se dominan asintoticamente, pues:
– |(n+1)2 |  4|n2|, para n  1.
– |n2|  |(n+1)2 | , para n  0.
Lo tengo
Notación O asintóticamente
dominado

 f(n) = O(g(n)) expresa: Que g(n) domina asintóticamente a f(n).

 La notación O significa que g(n) es el límite superior para el


crecimiento de f(n).

O(g(n)) = {f(n) : n constantes positivas c y n0, tal que n  n0,


tenemos 0  f(n)  cg(n) }

– Cualquier función lineal an + b esta en O(n2). ¿cómo?

– Mostar que 3n3 = O(n4) para un c y un n0 apropiado.


 Cuando la notación O se usa para expresar el tiempo de ejecución de un
algoritmo en el peor caso, entonces estaremos definiendo el limite superior del
tiempo de ejecución de tal algoritmo para todas las entradas.

 Ejemplo:
– Algoritmo de ordenación por inserción es en el peor caso O(n2).

Técnicamente es un abuso decir que el tiempo de ejecución del algoritmo de


ordenación por inserción es O(n2), sin especificar de que caso se trata.
a) El tiempo de ejecución de este algoritmo depende de cómo estén dados los
datos de entrada.

b) Si los datos de entrada ya estuviesen ordenados, entonces el algoritmo tiene


un tiempo de ejecución de O(n) en el mejor caso
Notación  Al fin
domino la
situación

 Especifica un límite inferior para el crecimiento de g(n).

(g(n)) = { f(n) :  constantes positivas c y n0, tal que n  n0,


tenemos 0  cg(n)  f(n) }

– Para mostrar que f(n) = 3n3 + 2n2 es (n3), basta hacer c = 1.


Entonces n3  3n3 + 2n2 , para n  0.

– f(n) es (n2), haciendo c = 1/10 y n = 0, 2, 4, . . .


 Cuando la notación  se usa para expresar el tiempo de ejecución de un
algoritmo en el mejor caso, entonces estaremos definiendo el limite inferior
del tiempo de ejecución de tal algoritmo para todas las entradas.

 Ejemplos:
1) El tiempo de ejecución del algoritmo de ordenación por inserción es, en el mejor
caso es (n).

2) Para mostrar que f(n) = 3n3 + 2n2 es (n3), basta hacer c = 1. Entonces n3  3n3
+ 2n2 , para n  0.

3) Sea  n, n impar n  1
f n    2
n 10 , n par n  0

Entonces f(n) es (n2), haciendo c = 1/10 y n = 0, 2, 4, . . .


Limites del algoritmo de ordenación por inserción

 De acuerdo con las notaciones anteriores, notamos que el tiempo de


ejecución del algoritmo esta entre (n) y O(n2).
Notación  Somos
iguales

(g(n)) = {f(n) :n constantes positivas c1, c2, y n0,


tal que n  n0, donde 0  c1g(n)  f(n)  c2g(n) }

 Sea f(n) = n2/3 – 2n. Dq: f(n) = (n2)


 Debemos obtener c1, c2 y n0 tal que:
c1n2  (1/3)n2 – 2n  c2n2,  n  n0 .
 Dividiendo tenemos:
c1  1/3 – 2/n  c2,  n  n0 .

• Lado derecho de la desigualdad es válido  n  1, cuando se escoge c2  1/3.


• Escogiendo c1  1/21, el lado izquierdo de la desigualdad es válido  n  7.
• Luego, considerando
– c1 = 1/21; c2 = 1/3; m=7

• Tenemos que: n2/3 – 2n = (n2).


Ejemplo: Muestre que n2
2
 
 3n   n 2

En efecto
Por definición

n2
c1n 
2
 3n  c2 n 2  n  n0
2

Dividiendo por n2 :
 n  1, c2  12
1 3
c1    c2
2 n
 n  7, c1  114

Por tanto :
n2
2
 3n   n 2  

se verifica para: c1  114 c2  1 2 n0  7


3) Análisis de algoritmos

 Determinar las características de la performance del algoritmo.


– Tiempo.
– Memoria.
– Ancho de banda de la comunicación.

 ¿Porque analizar algoritmos?


– De todos los algoritmos que se tenga, elegir aquel que sea el mas eficiente
para el mismo problema.

 ¿Es posible obtener el mejor tiempo de ejecución para un problema


razonablemente finito ?.
3.1 Costo de un algoritmo

 Determinar el menor costo para resolver problemas de una clase


dada.

 Cuando el Costo de un Algoritmo es igual al Menor Costo Posible,


entonces el algoritmo es óptimo.

 Pueden existir varios algoritmos para resolver el mismo problema.

 Si la misma medida de costo se aplica a diferentes algoritmos,


entonces se puede compararlos y escoger el mas adecuado.
 Ejemplo:
– Ordenación selección.

– Ordenación inserción.

– Ordenación Quicksort.

– Ordenación HeapSort.
3.2 Función de complejidad de un algoritmo

 Para medir el costo de ejecución de un algoritmo:


– Se debe definir la función de costo o función de complejidad f.

 f(n) denota la medida del tiempo necesario para ejecutar un algoritmo


para un problema de tamaño n.

 Función de complejidad de tiempo f(n):


– Mide el tiempo que es necesario para ejecutar un algoritmo en un problema
de tamaño n.

– La complejidad de tiempo no representa directamente tiempo.


 Esta complejidad representa la cantidad de veces que una operación se ejecuta.
 Función de Complejidad de Espacio f(n):
– Mide la cantidad de memoria necesaria para ejecutar un algoritmo en un
problema de tamaño n.

 Ejemplo
– Algoritmo: Hallar el mayor elemento del vector A[1..n]; n  1.

int max(vector A)

temp = A[0]
PARA (i = 1: I  n)
SI (temp  A[i] )
temp = A[i]
FINPARA
Retornar temp
– Análisis
 Sea f una función de complejidad, tal que f(n) es la cantidad de comparaciones
entre los elementos de A.

 Luego: f(n) = n –1, para n > 0.

– Este resultado es óptimo.

Teorema: Cualquier algoritmo que encuentra el mayor elemento de un


conjunto de n elementos, n  1, hace por lo menos n – 1
comparaciones.

Interpretación: El teorema dice, que si la cantidad de comparaciones


fuera utilizado como medida de costo, entonces la función Max del
programa anterior es optima.
3.3 Tamaño de la entrada de los datos

 La medida del costo de ejecución de un algoritmo depende del


tamaño de la entrada de los datos.

 Es común considerar el tiempo de ejecución de un algoritmo, como


una función.

 Para algunos algoritmos, el costo de ejecución es una función de la


entrada particular de los datos.
– En la función Max, el costo es uniforme sobre todos los problemas de
tamaño n.

– En un algoritmo de ordenación esto no ocurre: Si los datos de entrada ya


estuviesen casi ordenados, entonces el algoritmo trabaja menos.
3.4 Clases de comportamiento asintótico

 Complejidad constante: f(n) = O(1)


– El uso del algoritmo es independiente del tamaño de n.

– Las instrucciones del algoritmo son ejecutadas una cantidad fija de veces.

 Complejidad logarítmica: f(n) = O(log n)


– Se presenta, comúnmente, en algoritmos que resuelven un problema
transformándolo en problemas menores.

– El tiempo de ejecución puede ser considerado como menor que una


constante grande.
 Para n = 1000, log2 1000  10.

– Ejemplo: Busca binaria.


 Complejidad lineal: f(n) = O(n)
– Un pequeño trabajo se realiza sobre cada elemento de entrada.

– Esto es bueno para un algoritmo que tiene que procesar y/o producir n
elementos de entrada y/o salida.

– Ejemplo: Busca secuencial, teste de la planaridad de un grafo.

 Complejidad lineal logaritmica: f(n) = O(nlog n)


– Ocurre en algoritmos que solucionan un problema dividiéndolo en
subproblemas. Luego resuelve cada subproblema y finalmente agrupa las
soluciones.

– Es común en algoritmos basados en el paradigma dividir para conquistar.

– Ejemplo: Ordenación MergeSort.


 Complejidad cuadrática: f(n) = O(n2)
– Ocurre en algoritmos que presentan anillos (loops) uno dentro del otro.

– Es útil en algoritmos que usan datos relativamente pequeño.

– Ejemplo: Ordenación por selección e inserción.

 Complejidad exponencial: f(n) = O(2n)


– No son útiles del punto de vista practico.

– Se presentan cuando en la solución de los problemas se usa la fuerza


bruta.

– Ejemplo: Cartero viajante.


220 = 1048576

 Complejidad factorial: f(n) = O(n!)


– Su comportamiento es mucho peor que el caso exponencial.

– Se presentan cuando se usa la fuerza bruta en la solución del problema.

– 20! = 2432902008176640000
3.5 Jerarquía de funciones

 Del punto de vista asintótico esta dado por:

donde  y c son constantes arbitrarias con

Resultados:
3.6 Casos en complejidad computacional

 Mejor caso:
– Menor tiempo de ejecución sobre todas las entradas de tamaño n.

– Cantidad mínima de pasos para cualquier entrada.

– Esta medida no es muy útil. ¿porque?.


 Peor caso:
– Mayor tiempo de ejecución sobre todas las entradas de tamaño n.

– Máxima cantidad de pasos empleados para cualquier entrada.

– Es una medida mas tratable.

 Caso medio (o caso esperado):


– Media (promedio) de los tiempos de ejecución de todas las entradas de
tamaño n.

– En el análisis de este caso, se supone una distribución de probabilidad


sobre el conjunto de entradas de tamaño n y el costo medio se obtiene con
base en esa distribución.
Ejemplos

1) Registros de un archivo
– Consideremos el problema de accesar a los registros de un archivo.
 Cada registro tiene una única clave que se utiliza para recuperar los registros
del archivo.

– Problema: Dada una clave cualquiera, localizar el registro que contenga tal clave.

– Algoritmo: Busca secuencial.


– Sea f una función de complejidad tal que f(n) denota la cantidad de registros
consultados en el archivo (cantidad de veces que la clave de consulta es
comparada con la clave de cada registro).

Mejor caso:
f(n) = 1
El registro buscado es el primero en ser consultado

Peor caso:
f(n) = n
El registro buscado es el ultimo en ser consultado o no esta presente en el
archivo.

Caso medio:
f(n) = (n + 1)2
2) Mayor y menor elemento: versión 1
– Problema: Hallar el mayor y menor elemento de un vector de enteros A[1: n]; n  1.

– El algoritmo puede ser obtenido de aquel algoritmo que encuentra el mayor


elemento.

void maxmin1(vector A)

max = A[0]; min = A[0]
PARA (i = 1: i  n)
SI ( A[i]  max)
max = A[i]
SI ( A[i]  min)
min = A[i]
FINPARA

Análisis
– Sea f una función de complejidad tal que f(n) es el número de comparaciones entre
los elementos de A, si A tuviera n elementos.

– Luego, para todos los casos:


f(n) = 2(n-1), n > 0.
3.7 Comparación de programas

 Sin considerar las constantes de proporcionalidad, los programas


se evalúan comparando las funciones de complejidad,

 Ejemplos:
– Un programa con tiempo O(n) es mejor que otro programa con tiempo
O(n2).
– Un programa A emplea 100n unidades de tiempo para ser ejecutado, otro
programa B emplea 2n2 . ¿Cuál es el mejor?.
 Depende el tamaño del problema.

 Para n < 50, B es mejor que A.

 Para problemas con:


– n pequeño, es preferible usar aquel programa que presenta O(n2).

– n grande, O(n) es mejor que O(n2).


3.8 Técnicas para el análisis de los algoritmos

 Determinar el:
– Tiempo de ejecución puede ser un problema matemático complejo.

– Orden del tiempo de ejecución, sin considerar el valor de la constante,


puede ser una tarea simple.

 En el análisis del algoritmo se usa matemática discreta:


– sumatorias, productos, permutaciones, factoriales, ecuaciones de
recurrencia, etc...
 Las técnicas de análisis de algoritmos son:

– asignación, lectura o escritura: O(1).

– Sucesión de comandos:
 Esta determinado por el mayor tiempo de ejecución de cualquier comando de la
sucesión.

– Comando de decisión: SI
 Tiempo empleado por los comandos que están dentro del comando de
decisión, mas el tiempo para evaluar la condición SI, que es O(1).

– Anillo (loop): PARA


 Suma del tiempo de ejecución del contenido del anillo mas el tiempo para
evaluar la condición de terminación (generalmente O(1)), multiplicado por el
número de iteraciones.
– Procedimientos no recursivos:
 Cada procedimiento debe ser computado separadamente uno a uno, empezando
con los procedimientos que no llaman a otros procedimientos.

 Luego se evalúan los procedimientos que llaman a los procedimientos (los cuales
no llaman a otros procedimientos).

 El proceso se repite hasta llegar al programa principal.

– Procedimientos recursivos:
 Asocia una función de complejidad f(n) desconocida, donde n mide el tamaño de
los argumentos.
Ej.- for (int i= 0; i < K; i++) { algo_de_O(1) } => K*O(1) = O(1)

Si el tamaño N aparece como límite de iteraciones ...


Ej.- for (int i= 0; i < N; i++) { algo_de_O(1) } => N * O(1) = O(n)

Ej.- for (int i= 0; i < N; i++) { for (int j= 0; j < N; j++) { algo_de_O(1) } }
tendremos N * N * O(1) = O(n2)

Ej.- for (int i= 0; i < N; i++) { for (int j= 0; j < i; j++) { algo_de_O(1) } }
el bucle exterior se realiza N veces, mientras que el interior se realiza 1,
2, 3, ... N veces respectivamente.
En total, 1 + 2 + 3 + ... + N = N*(1+N)/2 -> O(n2)
A veces aparecen bucles multiplicativos, donde la evolución de la
variable de control no es lineal (como en los casos anteriores)
Ej.- c= 1; while (c < N) { algo_de_O(1) c= 2*c; }
El valor incial de "c" es 1, siendo "2k" al cabo de "k" iteraciones. El
número de iteraciones es tal que 2k >= N => k= eis (log2 (N)) [el entero
inmediato superior] y, por tanto, la complejidad del bucle es O(log n).
Ej.- c= N; while (c > 1) { algo_de_O(1) c= c / 2; }
Un razonamiento análogo nos lleva a log2(N) iteraciones y, por tanto, a
un orden O(log n) de complejidad.
Ej.- for (int i= 0; i < N; i++) { c= i; while (c > 0) { algo_de_O(1) c= c/2; } }
tenemos un bucle interno de orden O(log n) que se ejecuta N veces,
luego el conjunto es de orden O(n log n)
 Ejemplo (procedimiento no recursivo)
– Ordenar los n elementos de un conjunto A en orden ascendente:

Ordena (Vector A) O(max{1, n-i})=O(n-i)

INICIO
Por lo tanto:
(1) PARA (i = 1: i < n) O(1) Condición terminación O(1) O((n-i)x(n-1))

(2) min  i; O(1)

(3) PARA (j = i + 1: j  n) O(1)

(4) SI ( A[j - 1] < A[min - 1] )


O(1x(n-i)) = O(n-i) O(n-i)
O(1)
(5) min  j;
FINPARA
/*cambia A[min] y A[i]*/
Cantidad de
(6) x  A[min - 1]; A[min - 1]  A[i - 1]; A[i - 1]  x O(1) iteraciones

FINPARA
FIN
n 1

 (n  i) 
1
n ( n 1)
2  n2
2  n2  O (n 2 )