Está en la página 1de 75

Análisis y Diseño de Algoritmos I

2019

Claudia Pereira - Liliana Martinez


Análisis y Diseño de Algoritmos I
¿Qué es un algoritmo?
Procedimiento: secuencia de pasos elementales y precisos que pueden ejecutarse
para resolver una tarea

Algoritmo es un procedimiento computacional que satisface los siguientes criterios

1. toma un valor o conjunto de valores como entrada,


2. produce algún valor o conjunto de valores como salida,
3. termina en un número finito de pasos,
4. cada paso debe estar bien definido: claro y sin ambigüedades,
5. cada instrucción debe ser básica y factible (efectivo).
Problemas y Algoritmos
Problemas y Algoritmos: Ejemplo
Instancia del
problema
Problemas, Algoritmos y Programas
Problemas, Algoritmos y Programas
Análisis y diseño de algoritmos I
Problemas que tienen soluciones algorítmicas y tratables
computacionalmente

Análisis y diseño de algoritmos II


Problemas que tienen soluciones algorítmicas , problemas
“difíciles”
Análisis y Diseño de Algoritmos I
¿Qué clase de problemas puede resolver un algoritmo? Ejemplos:
• Desde problemas simples :
• Ordenar información para su posterior manipulación
• Dado un mapa de rutas en el que se marca la distancia entre cada par de ciudades
adyacentes, determinar la ruta más corta entre cada par de ciudades.

• Hasta más complejos :


• Comercio electrónico: bienes y servicios se negocian e intercambian electrónicamente .Es
necesario mantener la privacidad de la información personal.
• Biología: Manipulación de grandes volúmenes de datos.

Si bien no vamos a resolver directamente problemas muy complejos, si veremos


técnicas de diseño en los que se basan muchos de los algoritmos que resuelven
estos problemas de forma eficiente.
Análisis y Diseño de Algoritmos I: Contenido
Unidad 1: Análisis de eficiencia de algoritmos

Unidad 2: Tipos de datos abstractos

Unidad 3: Técnicas de diseño de algoritmos

Lenguaje de programación: C++


Análisis y Diseño de Algoritmos I: Contenido
Unidad 1: Análisis de eficiencia de algoritmos

Unidad 2: Tipos de datos abstractos

Unidad 3: Técnicas de diseño de algoritmos

Lenguaje de programación: C++

Metodología construcción de soluciones algorítmicas


Unidad 1

Análisis de eficiencia de algoritmos


Eligiendo un algoritmo
Dado un problema, pueden existir distintos algoritmos que lo resuelven

¿ Cómo elegir un algoritmo para resolver el problema?

• Simplicidad: un algoritmo simple es más fácil de implementar correctamente


que uno complejo y es menos probable que el programa
resultante tenga menos errores.

• Claridad: un algoritmo simple y comprensible es más fácil de describir, esto


permite que el programa resultante pueda ser escrito claramente y
documentado cuidadosamente para que otros puedan mantenerlos.

• Eficiencia: se refiere al uso eficiente de recursos como tiempo y espacio


• tiempo que tarda en ejecutarse
• espacio de almacenamiento de sus variables
Eligiendo un algoritmo
Dado un problema, pueden existir distintos algoritmos que lo resuelven

¿ Cómo elegir un algoritmo para resolver el problema?

• Simplicidad Problema: a menudo simplicidad y eficiencia son objetivos conflictivos.


Por ejemplo: Ordenamiento por selección (SelectionSort) y
• Claridad Ordenamiento por mezcla (mergesort)

• Eficiencia
Eligiendo un algoritmo
Problema: ordenamiento
Dados algoritmos de ordenamiento:
• SelectionSort
• Mergesort ¿Cuál elegir?
Eligiendo un algoritmo
Dado un problema, pueden existir distintos algoritmos que lo resuelven

¿ Cómo elegir un algoritmo para resolver el problema?

• Simplicidad Problema: a menudo simplicidad y eficiencia son objetivos conflictivos.


Por ejemplo: Ordenamiento por selección (selectionSort) y
• Claridad Ordenamiento por mezcla (mergesort)

• Eficiencia El segundo es más largo y un poco más difícil de entender que el primero.
Sin embargo, el segundo es mucho más eficiente el segundo.

En general, algoritmos que son eficientes para grandes cantidades de datos


tienden a ser más complejos de escribir y comprender
que los algoritmos relativamente ineficientes.
Eligiendo un algoritmo
Dado un problema, pueden existir distintos algoritmos que lo resuelven

¿ Cómo elegir un algoritmo para resolver el problema?

• Simplicidad Subjetiva: Podemos superarla explicando bien el algoritmo por


medio de comentarios y documentación.
• Claridad

• Eficiencia Objetiva: un programa toma el tiempo de ejecución necesario

Problema: ¿Cómo medimos el tiempo?


Ejecutar el programa para todas las entradas posibles, podrían ser infinitas!
El tiempo cambia de una máquina a otra……

Cómo medir el tiempo de ejecución es el tema de estudio Unidad 1.


Midiendo el tiempo de ejecución de un programa

Factores de los que depende el tiempo de ejecución de un programa

 Algoritmo

 Datos de entrada

 Computadora

 Sistema Operativo, compilador (calidad del código generado)


Midiendo el tiempo de ejecución de un programa
Dos tipos de análisis:

 Empírico
o Supone tener el código y ejecutarlo
o Se realiza ejecutando el programa sobre una colección de entradas.
o La medición depende de la computadora, SO, y del compilador
o El tiempo se mide en unidades de tiempo
Midiendo el tiempo de ejecución de un programa
Dos tipos de análisis:

 Empírico

 Teórico
o No se necesita el código
o Permite un análisis predictivo de la eficiencia
o No depende de las características de implementación
o ¿Cómo se mide el tiempo? Cantidad de trabajo realizado:
o cantidad de sentencias ejecutadas
o cantidad de operaciones relevantes que requiere la ejecución
Midiendo el tiempo de ejecución de un programa

Análisis teórico
Análisis depende del “tamaño de la entrada” del problema

1° Determinar qué representa la entrada en cada problema particular

Problema Tamaño de la entrada


Ordenar una lista de números ?
Encontrar a un nombre x en una lista de nombres ?
Multiplicar dos matrices ?
Recorrer un árbol binario ?
Midiendo el tiempo de ejecución de un programa

Análisis teórico
Análisis depende del “tamaño de la entrada” del problema

1° Determinar qué representa la entrada en cada problema particular

Problema Tamaño de la entrada


Ordenar una lista de números Cantidad de números a ordenar
Encontrar a un nombre x en una lista de nombres Número de nombres en la lista
Multiplicar dos matrices Dimensiones de las matrices
Recorrer un árbol binario Número de nodos en el árbol
Midiendo el tiempo de ejecución de un programa

Análisis teórico

2° Medir el “tiempo de ejecución” : Dada una entrada de tamaño n, determinar


matemáticamente el número de unidades de tiempo que necesita el programa para
su ejecución
1 unidad de tiempo  1 operación elemental de un programa
operación cuyo tiempo de ejecución se puede acotar por una constante que sólo
depende de una implementación particular, de la computadora y del lenguaje.

Tiempo de ejecución de un algoritmo :


número de operaciones elementales a ser ejecutados.
Midiendo el tiempo de ejecución de un programa

Análisis teórico

2° Medir el “tiempo de ejecución” : Dada una entrada de tamaño n, determinar


matemáticamente el número de unidades de tiempo que necesita el programa para
su ejecución

• peor caso
• mejor caso
• caso promedio
Midiendo el tiempo de ejecución - Ejemplo
Problema: ¿existe un número entero x en un arreglo de n enteros?
Tamaño del problema = n (cantidad de elementos en el arreglo)
Algoritmo: búsqueda secuencial
Análisis del tiempo: supongamos que medimos la cantidad de comparaciones
necesarias (#C) para encontrar el elemento x :
 mejor caso: x está en la primer posición del arreglo. #C = 1
 peor caso: x no está en el arreglo. #C = n
 caso promedio: asumiendo que x está en el arreglo y tiene
la misma probabilidad de que esté en una posición u otra. #C = ½ n
Midiendo el tiempo de ejecución - Ejemplo
Estimar el tiempo de ejecución de la siguiente función en el peor caso:

int hallarMax (int E[], int n) { // busca el entero máximo en un


1. int max = E[0]; // arreglo de n elementos
2. for (int indice = 0; indice  n; indice++)
3. if (max  E[indice])
4. max = E[indice];
5. return max;

Tamaño de la entrada: cantidad de elementos del arreglo (n)


Operaciones elementales : asignación, comparación, incremento y return
Midiendo el tiempo de ejecución - Ejemplo
Estimar el tiempo de ejecución de la siguiente función en el peor caso:

int hallarMax (int E[], int n) {


1. int max = E[0];
2. for (int indice = 0; indice  n; indice++) 2 n unidades
3. if (max  E[indice]) 1 n unidades
4. max = E[indice]; (peor caso) 1 n unidades
5. return max;

1° Analizamos el bucle for


itera n veces y se ejecutan 4 operaciones elementales c/ vez => 4 n unidades
Midiendo el tiempo de ejecución - Ejemplo
Estimar el tiempo de ejecución de la siguiente función en el peor caso:

int hallarMax (int E[], int n) {


1. int max = E[0]; 1u
2. for (int indice = 0; indice  n; indice++) 2 nu +2u
3. if (max  E[indice]) 1 nu
4. max = E[indice]; 1 nu
5. return max; 1u

1° Analizamos el bucle for


itera n veces y se ejecutan 4 operaciones elementales c/ vez => 4 n unidades
2° Sumamos sentencias que se ejecutan una sola vez: 4 unidades
Midiendo el tiempo de ejecución - Ejemplo
Estimar el tiempo de ejecución de la siguiente función en el peor caso:

int hallarMax (int E[], int n) {


1. int max = E[0]; 1u
2. for (int indice = 0; indice  n; indice++) 2 nu +2u
3. if (max  E[indice]) 1 nu
4. max = E[indice]; 1 nu
5. return max; 1u

# operaciones elementales = 4 n+4

El programa opera sobre el arreglo E [ 0 … n-1 ]


=> tiempo de ejecución sobre entradas de tamaño n es T(n) = 4 n + 4 unidades
Midiendo el tiempo de ejecución - Ejemplo
Estimar el tiempo de ejecución de un fragmento del algoritmo de
ordenamiento selectionSort

Fragmento:

Tamaño de la entrada: cantidad de elementos en A [ i … n-1 ]


Operaciones elementales : asignación, comparación e incremento
Midiendo el tiempo de ejecución - Ejemplo
Estimar el tiempo de ejecución de un fragmento del algoritmo de
ordenamiento selectionSort

Fragmento:
2 (n - i - 1 ) u
1 (n - i - 1 ) u
(peor caso) 1 (n - i - 1 ) u
1° Analicemos el bucle for
• For itera n - i - 1 veces
• Se ejecutan 4 sentencias elementales cada vez => 4 (n - i - 1 ) unidades
Midiendo el tiempo de ejecución - Ejemplo
Estimar el tiempo de ejecución de un fragmento del algoritmo de
ordenamiento selectionSort

Fragmento: 1u
2 (n - i - 1 ) u + 2u
1 (n - i - 1 ) u
1 (n - i - 1 ) u
1° Analicemos el bucle for
• For itera n - i - 1 veces
• Se ejecutan 4 sentencias elementales cada vez => 4 (n - i - 1 ) unidades

2° Sumamos sentencias que se ejecutan una sola vez: 3 unidades


Midiendo el tiempo de ejecución - Ejemplo

Estimar el tiempo de ejecución de un fragmento del algoritmo de


ordenamiento selectionSort

Fragmento: 1u
2 (n - i - 1 ) u + 2u
1 (n - i - 1 ) u
1 (n - i - 1 ) u

# operaciones elementales = 4 (n - i - 1 ) + 3 = 4 ( n – i ) – 1 unidades

El fragmento de programa opera sobre A [ i … n-1 ]


Sea m= n-i => tiempo de ejecución sobre entradas de tamaño m, T(m) = 4 m – 1 unid.
Ejercicio
Calcular el número de operaciones elementales de la función contar

¿Operaciones elementales?
¿Tamaño de la entrada?
Analizando Algoritmos

Llamaremos T(n) a la medida del tiempo de ejecución


que requiere un programa para una entrada de tamaño n

El tiempo de ejecución crece como una función del


tamaño de las entradas

Fragmento de programa selectionSort previo realiza 4m-1


operaciones elementales para una entrada de tamaño m
T(m) = 4 m – 1
Analizando Algoritmos
Dados dos algoritmos de ordenamiento:

• Alg 1: inserción
8n2 operaciones elementales

• Alg 2: mergesort
4 n log n operaciones elementales

¿Cuál de los dos algoritmos se ejecuta más rápido?


Analizando Algoritmos

8n2 crece más rápido que 64 n log n

A medida que n es cada vez más grande, la


constante de proporcionalidad no importa

Cuando consideramos los tamaños de entrada lo suficientemente grandes


como para hacer relevante solo el orden de crecimiento del tiempo de
ejecución, estamos estudiando la eficiencia asintótica de los algoritmos.
Analizando Algoritmos

Sea T(n) una función que expresa el tiempo de ejecución de un programa.


Asumiremos:
1. El argumento n es un entero no negativo
2. El valor de T(n) no es negativo para todo argumento n.
3. Acotaremos a T(n) por una función g(n) definida sobre enteros no negativos
usando las siguientes notaciones matemáticas:
 Big-Oh (Ο)
 Big-Omega (Ω)
 Big-Theta (Θ)
Notación asintótica
g es cota superior
O(g(n)) = { f(n):  constantes positivas c y n0 :
0 ≤ f(n) ≤ c g(n) n ≥ no }

Ω (g(n)) = { f(n):  constantes positivas c y n0 :


0 ≤ c g(n) ≤ f(n)  n ≥ no }
g es cota inferior

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


0 ≤ c1 g(n) ≤ f(n) ≤ c2 g(n)  n ≥ n0 }
g es cota inferior y superior
Para todas las notaciones: n0  N + y c1 y c2  R+
Notación asintótica
Sea g: N+  R+

 (g) es el conjunto de funciones que


crecen con mayor o igual rapidez que g

g  (g) es el conjunto de funciones que


crecen al mismo ritmo que g

 (g) es el conjunto de funciones que crecen


con menor o igual rapidez que g

Clasificación de funciones según el orden de crecimiento (forma de la función)


Notación asintótica

¿Cómo probar que f(n)  O(g(n)) o f(n)  Ω (g(n)) o f(n)  Θ (g(n))?

A partir de la definición formal de cada una de las notaciones

1. Establecer un par testigo (c, n0) o una 3-upla (c1, c2, n0) (notación Θ).
2. Por manipulación algebraica demostrar que para el par o 3-upla y  n ≥ n0 se cumple:
 f(n) ≤ c g(n) // para O
 f(n) ≥ c g(n) // para Ω
 0 ≤ c1 g(n) ≤ f(n) ≤ c2 g(n) // para Θ

Recordar: • n0  N+ y c1 y c2  R+.
• Todas las pruebas son esencialmente las mismas, solo la manipulación algebraica varía.
¿Cómo probar que f(n)  O(g(n))?
Dadas : f(n) =25 + 5 n2 y g(n) = n2 , ¿ f(n)  O(g(n)) ? // demostrar si n2 es cota superior de f

Partimos de la definición formal:


O(g(n)) = { f(n):  constantes positivas c y n0 : 0 ≤ f(n) ≤ c g(n) n ≥ no }
1. encontrar un par testigo (n0, c) tal que satisfaga 25 + 5 n2 ≤ c n2 n ≥ no
despejar c  25/n2 + 5 ≤ c y dar valores a n

2. tomando como par testigo n0= 1 y c= 30, veamos si se cumple la desigualdad n ≥ n0

para n=1 → 25 + 5 12 ≤ 30 12 30 ≤ 30 
n=2 → 25 + 5 22 ≤ 30 22 45 ≤ 120 
n=3 → 25 + 5 32 ≤ 30 32 70 ≤ 270 

Para n0= 1 y c= 30 ,  n ≥ n0 se cumple que 25 + 5 n2 ≤ 30 n2,  25 + 5 n2  O(n2)
¿Cómo probar que f(n)  O(g(n))?
O(g(n)) = { f(n):  constantes positivas c y n0 : 0 ≤ f(n) ≤ c g(n) n ≥ no }

Dadas : f(n) = 25 + 5 n2 y g es cota superior


g(n) = n2 ,
c
par testigo
n0= 1 y c= 30 30 n2

f(n)  O(g(n)) n0 25 + 5 n2
¿Cómo probar que f(n)  O(g(n))?
Para el mismo par de funciones ¿ podríamos haber hallado otro par testigo?
Dadas f(n) =25 + 5 n2 y g(n) = n2 , ¿ f(n)  O(g(n)) ?

1. encontrar (n0, c) : 25 + 5 n2 ≤ c n2

2. Elegimos n0= 5 y c= 6, veamos si se cumple la desigualdad n ≥ n0


para n= 6 → 25 + 5 62 ≤ 6 62 205 ≤ 216 
n= 7 → 25 + 5 72 ≤ 6 72 270 ≤ 294 

Para n0= 5 y c= 6 ,  n ≥ n0  se cumple 25 + 5 n2 ≤ c n2  25 + 5 n2  O(n2)

Existen otras elecciones de constantes, pero lo importante es que “alguna elección exista”
¿Cómo probar que f(n)  O(g(n))?
O(g(n)) = { f(n):  constantes positivas c y n0 : 0 ≤ f(n) ≤ c g(n) n ≥ no }

Dadas : f(n) = 25 + 5 n2 g es cota superior

y g(n) = n2 , 6 n2
c
par testigo
n0= 5 y c= 6

f(n)  O(g(n)) 25 + 5 n2
n0
¿Cómo probar que f(n)  O(g(n))?
O(g(n)) = { f(n):  constantes positivas c y n0 : 0 ≤ f(n) ≤ c g(n) n ≥ no }

Dadas : f(n) = 25 + 5 n2
y g(n) = n2 , 12 n2

A partir de la gráfica,
deducir un par testigo
25 + 5 n2
n0 = ?

C=?
¿Cómo probar que f(n)  O(g(n))?
O(g(n)) = { f(n):  constantes positivas c y n0 : 0 ≤ f(n) ≤ c g(n) n ≥ no }

Dada f(n) = an2 + bn +c podemos decir que f(n)  O(n2) -- a,b y c constantes

Demostración:

 constantes positivas c0 y n0 : an2 + bn +c ≤ c0 n2 n ≥ no

Dividimos por n2 a + b/n +c/n2 ≤ c0

a + b/n +c/n2 ≤ a + b + c ≤ c0

Hacemos c0 = a+b+c y no = 1 la desigualdad se cumple  n ≥ no


¿Cómo probar que f(n)  O(g(n))?
O(g(n)) = { f(n):  constantes positivas c y n0 : 0 ≤ f(n) ≤ c g(n) n ≥ no }

En general, para cualquier polinomio

𝑘
p (n) = 𝑖=0 𝑎𝑖 𝑛𝑖 , donde las 𝑎𝑖 son constantes y 𝑎𝑛 > 0

p (n)  O ( nk )

Ejercicio: Demostrar
¿Cómo probar que f(n)  O(g(n))?
Probamos que f(n)  O(g(n)) partiendo de la definición y encontrando un par testigo
¿Cómo probar que f(n)  O(g(n))? Partimos de la definición formal de Big-Oh,
asumir que el par testigo (n0 , c) existe, y derivar una contradicción.

Ejemplo: Dadas f(n) = n2 y g(n) = n, ¿ f(n)  O(g(n)) ?


Supongamos que si , entonces por la definición de Big-Oh  (n0, c) :
n2 ≤ c n , n ≥ n0
dividimos por n -> n ≤ c

No es cierto para n arbitrariamente grande. Estaríamos afirmando que una


constante acota a una función que crece,  ∄ tal par testigo
f(n)  O(g(n))
¿Cómo probar que f(n)  (g(n))?
Ω (g(n)) = { f(n):  constantes positivas c y n0 : 0 ≤ c g(n) ≤ f(n)  n ≥ no }

Dadas : f(n) = ½ n2 -3n


y g(n) = n2
½ n2 – 3n
buscar un (n0, c ) :
c n2 ≤ ½ n2 – 3n
c ≤ ½ – 3*1/n
C g es cota inferior

n0 = 12, c = ¼ ¼ n2
n0
f(n)   (g(n))
¿Cómo probar que f(n)  (g(n))? Ejercicio
Ω (g(n)) = { f(n):  constantes positivas c y n0 : 0 ≤ c g(n) ≤ f(n)  n ≥ no }

Dada f(n) = an2 + bn +c podemos decir que f(n)  Ω(n2) -- a,b y c constantes

Demostrar
¿Cómo probar que f(n) Î (g(n))?
Θ (g(n)) = { f(n):  constantes positivas c1, c2 y n0: 0 ≤ c1 g(n) ≤ f(n) ≤ c2 g(n)  n ≥ n0 }

Dadas dos funciones cualesquiera f(n) y g(n),

f(n)   (g(n)) f(n)  O (g(n)) y f(n)   (g(n))


¿Cómo probar que f(n)  (g(n))?
Θ (g(n)) = { f(n):  constantes positivas c1, c2 y n0: 0 ≤ c1 g(n) ≤ f(n) ≤ c2 g(n)  n ≥ n0 }

Dadas : f(n) = ½ n2 -3n ½ n2 – 3n


y g(n) = n2 g es cota superior

c2
buscar un (n0, c1, c2 ) : n ≥ n0
c1 n2 ≤ ½ n2 – 3n ≤ c2 n2 ½ n2 ¼ n2
Dividimos por n2 c1
c1 ≤ ½ – 3/n ≤ c2
g es cota inferior

n0 = 12, c1 = ¼ , c2 = ½ n0= 12

f(n)   (g(n))
¿Cómo probar que f(n)  (g(n))?
Θ (g(n)) = { f(n):  constantes positivas c1, c2 y n0: 0 ≤ c1 g(n) ≤ f(n) ≤ c2 g(n)  n ≥ n0 }

Dadas : f(n) = 6 n3 y g(n) = n2 , ¿f(n)   (g(n))?

Supongamos para el propósito de contradicción que existen n0 y c2 :


6 n3 ≤ c2 n2 n ≥ n0
Dividimos por 6n2

n ≤ c2/6
Lo cual no es posible para un n arbitrariamente grande, ya que c2 es constante.

f(n)   (g(n))
Ejercicios
1. Para cada par de funciones:
1.1. f(n) = 10 n + 15 y g(n) = n
1.2. f(n) = ½ n2 +1000 y g(n) = n3

Determine si: a) f(n)  O (g(n)) b) f(n)   (g(n)) c) f(n)   (g(n))

2. Para cada uno de los siguientes pares de funciones:


2.1. f(n) = 2n+1 y g(n) = 2n
2.2. f(n) = 22n y g(n) = 2n

Determine si f(n)  O (g(n))


Ejercicios
Para el par de funciones : f(n) = 15 n +10 y g(n) = n
y a partir de la gráfica :
25 g(n)

15n +10
De valores a la tripla
(c1,c2 y n0) tal que
se cumpla que 15 g (n)

f(n)   (g(n))
Describiendo el tiempo de ejecución de un programa
Sea T(n) el tiempo de ejecución de un programa en el “peor de los casos”
para una entrada de tamaño n.

Partiendo de: O(g(n)) = { f(n):  constantes positivas c y n0 : 0 ≤ f(n) ≤ c g(n) n ≥ no }

T(n)  O(g(n)) si  constantes c  R+ y n0  N+ :


T (n) ≤ c g(n) n ≥ no

constante de proporcionalidad complejidad temporal


factores que dependen de la implementación: factores que no dependen de la implementación:
computadora, compilador, SO algoritmos, entradas (tamaño de la entrada)
Describiendo el tiempo de ejecución de un programa
Ejemplo:
c Sean T(n) = 30 + 10n+ 100n2 + 2. n3
3 n3
y g (n) = n3

Para (c= 3, n 0 =101) y n ≥ n o


se cumple:T (n) ≤ c g(n)
T(n) = 30 + 10n+ 100n2 + 2. n3

T(n)  O(g(n))

• g(n) es un término simple

• g(n) es la cota “más cercana”


n0= 101
en notación big-Oh
Describiendo el tiempo de ejecución de un programa

Características de la función de la complejidad temporal g(n):

 simplicidad:
• g(n) es un término simple
• el coeficiente del término es 1

 g(n) es la cota “más cercana” en notación big-Oh


Describiendo el tiempo de ejecución de un programa
Características de la función de la complejidad temporal g(n):

 simplicidad: el coeficiente del término es 1

Si k constante y T(n)  O( k g(n) )  T(n)  O( g(n) )

Demostración: Si T(n)  O(k f(n)) entonces por definición:


 constantes positivas c y n0 : 0 ≤ f(n) ≤ c k g(n) n ≥ no
sea c1 = c k > 0,
luego  c1 y n0 : f(n) ≤ c1 g(n) n ≥ no , => T(n)  O( g(n) )
Describiendo el tiempo de ejecución de un programa
Características de la función de la complejidad temporal g(n):

 g(n) es la cota “más cercana” en notación big-Oh


Ejemplo: dada T(n) = ½ n2 , T(n)  O(n2)  más cercano
T(n)  O(n3)
T(n)  O(n4) …

Por otro lado, como una expresión de tiempo de ejecución, nos gustaría decir
T(n)  (0.5 n2) porque es "más estricto”.
Pero…. El coeficiente del término es 1, no nos interesa el valor exacto sino la
forma de la función
Aritmética en notación O
Regla para la suma:

A TA(n)
Programa P TP (n) = TA(n) + TB(n)
TB(n)
B

La complejidad de P será igual a la complejidad del bloque más costoso.


Aritmética en notación O
Regla para la suma: Desmostración
O(g(n)) = { f(n):  constantes positivas c y n0 : 0 ≤ f(n) ≤ c g(n) n ≥ no }

A TA (n)  O(g(n)) =>  c1 y n1 > 0 : n ≥ n1 TA(n) ≤ c1 g(n)

TB (n)  O(h(n)) =>  c2 y n2 > 0 : n ≥ n2 TB(n) ≤ c2 h(n)


B

TP (n) = TA(n) + TB(n) ≤ c1 g(n) + c2 h(n )


TP (n) ≤ c1 max ( g(n) , h(n) ) + c2 max ( g(n) , h(n) )

TP (n) ≤ (c1 + c2) max ( g(n) , h(n) ) , c0 = (c1 + c2) y n0 = max ( n1 , n2 )

luego:  c0 y n0 > 0 tal que  n ≥ n0 TP (n) ≤ c0 . max ( g(n) , h(n) )

TP(n)  O ( max ( g(n) , h(n) )


Analizando el tiempo de ejecución de un programa
Tiempo de ejecución de sentencias simples
Contienen sólo operaciones simples (independientes del tamaño de la entrada):
• Operaciones aritméticas
• operaciones lógicas
• operaciones de comparación
• operaciones de acceso a variables, miembros de estructuras o a elementos
de un arreglo.
• asignaciones simples
• entrada y salida de datos simples

Se ejecutan en un tiempo constante: O(1)


Analizando el tiempo de ejecución de un programa

Tiempo de ejecución de un bloque

<Sentencia 1> → O (f(n)) TBloque(n) ≤ c1 f(n) + c2 g(n) + … ck h(n)


<Sentencia 2> → O (g(n))
Por la regla de la suma:

<Sentencia k> → O (h(n)) TBloque (n)  max ( f(n), g(n), …, h(n) )

El tiempo de ejecución de una secuencia de sentencias que conforman un bloque


está determinado por la regla de la suma.
Analizando el tiempo de ejecución de un programa
Tiempo de ejecución de sentencias de selección:

if (<condición>) → O (f(n))
<Sentencia S1> → O (g(n))
else
<Sentencia S2> → O (h(n))

Tif (n) ≤ t cond + c max (TS1, TS2) //solo se ejecuta una rama

Tif (n) ≤ c3 f(n) + max (c1 g(n), c2 h(n) )

Nota: Si la condición es O(1), entonces Tif (n)  O ( max (g(n) , h(n) ) )


La rama del else es opcional, sino está, entonces : T(n)  t cond + c g(n)
Analizando el tiempo de ejecución de un programa
Tiempo de ejecución de una iteración:

while ( condición ) → O (f(n)) # interaciones O(g(n))


<Sentencia> → O (h(n))

Tloop = Tcondición + ( Tcondición + TSentencia ) * # iteraciones


Tloop <= c1 f(n) + ( c1 f(n) + c2 h(n) ) * c3 g(n) //aplicando regla de la suma , c4= max (c1,c2)
Tloop <= c1 f(n) + c4 max ( f(n) , h(n) ) * c3 g(n) // c0 = c4 c3
Tloop <= c1 f(n) + c0 max ( f(n) , h(n) ) * g(n)
Tloop  O ( max (f(n), h(n)) * g(n) )
// Si el tiempo de la condición es O(1) => Tloop  O ( h(n) * g(n) )
Analizando el tiempo de ejecución de un programa: Ejemplos

Función que intercambia el valor de dos variables enteras


// Tamaño de la entrada: constante

void intercambiar (int& x, int& y)


T(𝑛)  c0 + 3 𝑐1
{ int aux;

aux= x; T( 𝒏 ) ϵ O ( 𝟏 )
x= y;

y= aux;
}
Analizando el tiempo de ejecución de un programa: Ejemplos

Función que imprime los primeros n números naturales


//Tamaño de la entrada = e𝒍 𝒏ú𝒎𝒆𝒓𝒐 𝒅𝒆 𝒆𝒏𝒕𝒆𝒓𝒐𝒔 𝒂 𝒊𝒎𝒑𝒓𝒊𝒎𝒊𝒓

void imprimir (int n) { Tloop = Tcond + (Tcond + TSent) * #iter

int i= 1; T(n)  c0 + n
c1 + c2 + c3 + c1
i=1

while (i <= n) { c = c1 + c2 + c3

cout << i << endl;  c0 +c n + c1

i++; } T( 𝒏 ) ϵ O ( 𝒏 )
}
Analizando el tiempo de ejecución de un programa: Ejemplos

Fragmento: busca el elemento más pequeño en un arreglo


... // Tamaño de la entrada = la dimensión del arreglo (𝒏)

menor= 0;
T(𝑛)  c0 + c1 + 𝑛−1
𝑖=1 𝑐2 + 𝑐3 + 𝑐4
for (int j= 1; j < n; j++)
𝑐 = 𝑐2 + 𝑐3 + 𝑐4
if (A[j] < A[menor])  c0 + c1 + c (n – 1)
ϵ O (𝟏)
menor = j;  c0 + c1 + c n – c

... T( 𝒏 ) ϵ O ( 𝒏 )
Analizando el tiempo de ejecución de un programa: Ejemplos

Fragmento que inicializa una matriz


... // Tamaño de la entrada = la dimensión de la matriz (𝒏 𝒙 𝒏)

menor= 0; 𝑛−1 𝑛−1


T(n) <= c1 + 𝑖=0 ( 𝑐2 + 𝑗=0 𝑐3 )

for (int i= 0; i < n; i++) c1 + 𝑛−1


𝑖=0 ( 𝑐2 + 𝑛 𝑐 3 )
for (int j= 0; j < n; j++) c1 + 𝑛 ( 𝑐2 + 𝑛 𝑐3 )
matriz[i][j] = 0; c1 + 𝑛 𝑐2 + 𝑛2 𝑐3
...
T( 𝒏 ) ϵ O ( 𝒏𝟐 )
Analizando el tiempo de ejecución de un programa_Ejercicio

Calcular el tiempo de ejecución de la función transpuesta :

void Transpuesta (int matriz [][n]) {


for (int i= 0; i < n; i++)
for (int j= i+1; j < n; j++)
{
int aux= Matriz [i][j];
matriz [i][j]= matriz [j][i];
matriz [j][i]= aux;
}
}
Tiempo de ejecución
Algunos tiempos de ejecución en notación Big-Oh más comunes:

O(1) constante
O(log n) logarítmico
O(2n)
O(n) lineal
polinomial O(n!) exponencial
O(n log n) n log n
O(nn)
O(n2) cuadrático
O(n3) cúbico
Tiempo de ejecución como función del tamaño de la entrada

El tiempo de ejecución “crece” como una función del tamaño de las entradas
David Harel (2012)
Ritmo de crecimiento de algunas funciones
Bibliografía

 Cormen, T.; Lieserson, C.; Rivest, R. Introduction to Algorithms,

3rd Edition. The MIT Press. 2009.

 Aho, A., Ullman, J. Foundations of Computer Science. C Edition.


Computer Science Press, 1995.

 Baase, Sara; Van Gelder, Allen. Computer Algorithms. Introduction to


Design and Analysis. 3rd Edition. Addison Wesley, 1999.

También podría gustarte