Вы находитесь на странице: 1из 10

1.2.

3 Notación Asintótica

La notación asintótica se usa para expresar la tasa u orden de crecimiento del tiempo de
ejecución de un algoritmo en términos del tamaño de la entrada n. Para esto es necesario
tomar en cuenta ciertas consideraciones.
Al descartar los términos menos significativos y los coeficientes constantes, es posible
concentrarse en la parte importante del tiempo de ejecución de un algoritmo sin
involucrarnos en detalles que complican el entendimiento. Cuando se descartan los
coeficientes constantes y los términos menos significativos, se usa notación asintótica.
Además, es importante simplificar la función para extraer la parte más importante y dejar
de lado las partes menos importantes para así determinar el orden de crecimiento. Por lo
tanto, habrá que enfocarse en qué tan rápido crece una función con respecto al tamaño de
la entrada.
Por ejemplo, si tenemos la función T(n)= 6n2+100n+300, podemos observar que el
término 6n2 es el que domina el crecimiento, ya que se vuelve más grande que el resto de
los términos (100n+300), una vez que n se hace suficientemente grande.

Para la notación asintótica se tienen los casos O, Ω y .

1.2.4 Funciones de orden


Toda función tiene una tasa de crecimiento, y en comparación con otras podemos evaluar
si esta crece más rápido o más lento. Si un algoritmo tiene una función de crecimiento n,
y otro de crecimiento n2, entonces podríamos decir que el primer algoritmo es mejor
porque su tasa de crecimiento o complejidad es menor.
Las funciones de orden u órdenes de complejidad, lo que indica es cómo se ordenan las
funciones de acuerdo a su complejidad, generalmente de menor a mayor. También
permite clasificar dichas funciones en polinómicas o exponenciales.
Las funciones más utilizadas para la tasa de crecimiento son:
n log2 n ,1, n, log2n, nn, an, n2, 2n, n!, na

Ejercicio: Ordenar las funciones de acuerdo a su tasa de crecimiento, de menor a mayor.


Hacer una gráfica por cada función y una gráfica para todas las funciones.
Función Valores aproximados
n 10 100 1000
n log2 n 33 664 9966
Polinómicas
n3 1000 1,000,000 109
106n8 1014 10 22
1030
2n 1024 1.27 x 1030 1.07 x 10301
Exponenciales 158
n! 3,628,800 10 4 x 102567

Los algoritmos polinómicos toman ventaja de los avances tecnológicos. Por ejemplo, a
cada momento se rompen los límites, por tanto, la velocidad de una computadora
incrementa, así el tamaño de la instancia más grande que puede resolver un algoritmo
polinómico en una hora, por ejemplo, puede ser multiplicado por una constante entre 1 y
10. En contraste, un algoritmo exponencial experimentará un suma o adición en el
incremento del tamaño de la instancia que puede resolver en un determinado tiempo.
Otra propiedad importante de los algoritmos polinómicos es qué al combinarlos para la
solución de un problema, el algoritmo resultante sigue siendo polinómico.

1.2.5 Notación O grande, Ω, Θ.

Cota Superior. Notación O


Dada una función f(n), queremos estudiar aquellas funciones g(n) que a lo sumo crecen
tan deprisa como f(n). Al conjunto de tales funciones se le llama cota superior de f(n) y
lo denominamos O(f(n)). Conociendo la cota superior de un algoritmo podemos asegurar
que, en ningún caso, el tiempo empleado será de un orden superior al de la cota.

Intuitivamente, f(n) ∈ O(g(n)) indica que f(n) está acotada superiormente por algún
múltiplo de g(n). Normalmente estaremos interesados en la menor función g(n) tal que
f(n) pertenezca a O(g(n)).

O(𝑔(𝑛)) = {𝑓 (𝑛): 𝑠𝑖 𝑒𝑥𝑖𝑠𝑡𝑒𝑛 𝑐𝑜𝑛𝑠𝑡𝑎𝑛𝑡𝑒𝑠 𝑝𝑜𝑠𝑖𝑡𝑖𝑣𝑎𝑠 𝑐 𝑦 𝑛0 𝑡𝑎𝑙 𝑞𝑢𝑒 0 ≤ 𝑓 (𝑛)


≤ 𝑐𝑔(𝑛) 𝑝𝑎𝑟𝑎 𝑡𝑜𝑑𝑜 𝑛 ≥ 𝑛0 }

Dado que O(g(n)) es un conjunto, escribimos f(n)= O(g(n)) para indicar que f(n) es
miembro de O(g(n)), o f(n)  O(g(n)).
La definición de O (g(n)) requiere que cada miembro de O (g(n) sea positivo o no-
negativo, esto es, que f(n) sea no- negativa aun cuando n sea suficientemente grande.
Esta notación se utilizará para indicar cotas superiores asintóticas.

Ejemplo de funciones que están en O(n2)

Ejemplo:
2n2 = O(n3)
con c= 1 y n0=2

Cota Inferior. Notación Ω


Dada una función f(n), queremos estudiar aquellas funciones g(n) que a lo sumo crecen
tan lentamente como f(n). Al conjunto de tales funciones se le llama cota inferior de f(n)
y lo denominamos Ω(f(n)). Conociendo la cota inferior de un algoritmo podemos asegurar
que, en ningún caso, el tiempo empleado será de un orden inferior al de la cota.

Intuitivamente, f(n) ∈ Ω(g(n)) indica que f(n) está acotada inferiormente por algún
múltiplo de g(n). Normalmente estaremos interesados en la menor función g(n) tal que
f(n) pertenezca a Ω(g(n)).

Ω(𝑔(𝑛)) = {𝑓 (𝑛): 𝑠𝑖 𝑒𝑥𝑖𝑠𝑡𝑒𝑛 𝑐𝑜𝑛𝑠𝑡𝑎𝑛𝑡𝑒𝑠 𝑝𝑜𝑠𝑖𝑡𝑖𝑣𝑎𝑠 𝑐 𝑦 𝑛0 𝑡𝑎𝑙 𝑞𝑢𝑒 0 ≤ 𝑐𝑔(𝑛)


≤ 𝑓 (𝑛) 𝑝𝑎𝑟𝑎 𝑡𝑜𝑑𝑜 𝑛 ≥ 𝑛0 }
Dado que Ω(g(n)) es un conjunto, escribimos f(n)= Ω(g(n)) para indicar que f(n) es
miembro de Ω(g(n)), o f(n)  Ω(g(n)).

La definición de Ω(g(n)) requiere que cada miembro de Ω (g(n)) sea positivo o no-
negativo, esto es, que f(n) sea no- negativa aun cuando n sea suficientemente grande.
Esta notación se utilizará para indicar cotas inferiores asintóticas.

Ejemplo de funciones que están en Ω(n2)

Ejemplo:
2
√𝑛 = Ω(𝑙𝑜𝑔𝑛)
con c=1 y n0=16
Cota Inferior. Notación 
Dada una función f(n), queremos estudiar aquellas funciones g(n) que a lo sumo crecen
tan lentamente como f(n) y a lo sumo crecen tan rápido como f(n). Al conjunto de tales
funciones se le llama cota inferior y cota superior de f(n) o cota ajustada y lo
denominamos (f(n)). Conociendo la cota superior e inferior de un algoritmo podemos
asegurar que, en ningún caso, el tiempo empleado será de un orden inferior o superior al
de la cota.

Intuitivamente, f(n) ∈ (g(n)) indica que f(n) está acotada inferiormente y superiormente
por algún múltiplo de g(n). Normalmente estaremos interesados en la función menor g(n)
tal que f(n) pertenezca a (g(n)).

Θ(𝑔(𝑛)) = {𝑓 (𝑛): 𝑠𝑖 𝑒𝑥𝑖𝑠𝑡𝑒𝑛 𝑐𝑜𝑛𝑠𝑡𝑎𝑛𝑡𝑒𝑠 𝑝𝑜𝑠𝑖𝑡𝑖𝑣𝑎𝑠 𝑐1,


𝑐2 𝑦 𝑛0 𝑡𝑎𝑙 𝑞𝑢𝑒 0 ≤ 𝑐1𝑔(𝑛) ≤ 𝑓 (𝑛) ≤ 𝑐2 𝑔(𝑛) 𝑝𝑎𝑟𝑎 𝑡𝑜𝑑𝑜 𝑛 ≥ 𝑛0 }

1
Ejemplo: 2 𝑛2 − 3𝑛= (n2)

c1=1/2 c2=1/14 n0=7

Teorema
Para cualesquiera dos funciones f(n) y g(n), f(n) = (g(n)) si y sólo si f(n) = O(g(n)) y
f(n) = Ω(g(n)).
Comparación de funciones
Muchas de las propiedades de los números reales pueden ser aplicadas a comparaciones
asintóticas. Asumimos que tanto f(n) como g(n) son asintóticamente positivas.
Transitividad
f(n) = (g(n)) y g(n)= (h(n)) implica que f(n) = (h(n))
f(n) = O(g(n)) y g(n)= O(h(n)) implica que f(n) = O(h(n))
f(n) = Ω(g(n)) y g(n)= Ω(h(n)) implica que f(n) = Ω(h(n))

Reflexividad
f(n) = (f(n))
f(n) = O(f(n))
f(n) = Ω(f(n))

Simetría
f(n) = (g(n)) si y sólo si g(n)= (f(n))

Simetría Transpuesta
f(n) = O(g(n)) si y sólo si g(n)= Ω(f(n))

Principio de invarianza
Podemos ver que la definición de la tasa de crecimiento es invariante ante constantes
multiplicativas, es decir:
f(n) = (cf(n)) => f(n) =  (f(n))
Ejemplo:
Si f(n) = (2n3), entonces también es (n3)
Reglas para el cálculo de la complejidad de los algoritmos
A. Si f(n), g(n) = O(h(n)) => f(n)+g(n) = O(h(n))
B. Sea k una constante, f(n) = O(g(n)) => k*f(n) = O(g(n))
C. Si f(n) = O(h1(n)) y g(n) = O(h2(n)) => f(n)+g(n) = O(h1(n)+h2(n))
D. Si f(n) = O(h1(n)) y g(n) = O(h2(n)) => f(n)*g(n) = O(h1(n)*h2(n))
E. Sean los reales 0 < a < b => O(na) es subconjunto de O(nb)
F. Sea P(n) un polinomio de grado k => P(n) = O(nk)
Las reglas de la suma y el producto son muy útiles cuando se calcula la complejidad de
un algoritmo (C y D).

Regla de la suma
Esta regla es la básica para analizar el concepto de secuencia en un programa: la
composición secuencial de dos trozos de programa es de orden de complejidad de la suma
de sus partes.
Consideramos, en primer lugar, que T1(n) y T2(n) son los tiempos de ejecución de dos
segmentos de programa, Pr1 y Pr2, y que T1(n) es O(f(n)) y T2(n) es O(g(n)).
Entonces el tiempo de ejecución de Pr1 seguido de Pr2, es decir T1(n) + T2(n), es
O(max(f(n), g(n))).
La definición es la siguiente:
c1,c2  R,  n1, n2  N: n ≥ n1  T1(n)≤ c1f(n), n≥n2  T2(n) ≤c2 g(n)
Por tanto, sea n0 = max(n1 , n2). Si n ≥ n0, entonces
T1(n) + T2(n) ≤ c1f(n) + c2g(n)
luego,
n ≥ n0  T1 (n) + T2 (n) ≤ (c1 + c2)max(f(n), g(n))
con lo que el tiempo de ejecución de Pr1 seguido de Pr2 es O(max(f(n), g(n))).

Regla para los productos


Consideramos que si T1(n) y T2(n) son los tiempos de ejecución de dos segmentos de
programa, Pr1 y Pr2, T1(n) es O(f(n)) y T2(n) es O(g(n)), entonces T1(n)×T2(n) es
O(f(n)×g(n)).
De esta la regla del producto se deduce que O(c f(n)) es lo mismo que O(f(n)) si c es una
constante positiva, así que por ejemplo O(n2/2) es lo mismo que O(n2).

La combinación de las reglas E y F permiten olvidar todos los componentes de un


polinomio, menos su grado.
Reglas para determinar la complejidad de cada sentencia/instrucción de un
algoritmo
Los algoritmos tienen cualquiera de las siguientes instrucciones:
• Sentencias/instrucciones sencillas.
• Condicionales (if).
• Bucles.
• Llamadas a funciones, procedimientos
Cada una de estas tiene cierta complejidad en notación asintótica O, las cuales se explican
a continuación.

Sentencias simples o sencillas


Los bloques de sentencias simples se ejecutan consecutivamente. Si el tiempo de
ejecución de estas sentencias es O(1), entonces el bloque completo tendrá ese tiempo de
ejecución por aplicación directa de la regla de la suma. Así, cualquier número constante
de O(1), suma O(1).

Ciclos
Ciclo for. En el caso más simple, en el que el tiempo consumido en el cuerpo del ciclo
sea el mismo en cada iteración, podemos multiplicar la cota superior O del cuerpo del
ciclo por el número de veces que este se ejecuta. Estrictamente hablando, deberíamos
añadir el tiempo O(1) para inicializar el índice del ciclo con el tiempo O(1) de la primera
comparación del índice del ciclo con su límite, pero salvo que el ciclo vaya a ejecutarse
cero veces, estos tiempos podemos obviarlos debido a la regla de la suma.
Si O(f(n)) es nuestra cota superior del tiempo de ejecución del cuerpo del ciclo y g(n) es
una cota superior del número de veces que se efectuara ese ciclo, siendo g(n) al menos 1
para todo n, entonces O(f(n)xg(n)) es una cota superior para el tiempo de ejecución del
for.

También hay casos en los que el número de iteraciones no está relacionado directamente
con n por ejemplo for j = i+1 to n.
La fórmula "Limite superior menos limite inferior mas uno" da: n - (i+1) + 1, es decir,
n-i como número de iteraciones del for. En definitiva, el tiempo consumido en el for es
(n-i) x O(1), es decir O(n-i). Pero si no hubiera certeza de que n-i fuera positivo, este
tiempo habría que expresarlo como O(max(1, n-i)). Para cuestiones prácticas, la
complejidad queda generalmente como O(n).
Ciclos While, do-while, repeat. Sea O(f(n)) la cota superior del tiempo de ejecución del
cuerpo del ciclo. Sea g(n) la cota superior del número de veces que puede hacerse el ciclo,
siendo al menos 1 para algún valor de n, entonces O(f(n)*g(n)) es una cota superior del
tiempo de ejecución del ciclo while. Para do-while y repeat, g(n) siempre vale al menos
1.

Condicionales
Supongamos que no hay llamadas dentro de la condición, y que las partes if y else tienen
cotas f(n) y g(n) respectivamente en notación O. Supongamos también que f(n) y g(n) no
son ambas cero, es decir, que mientras la parte else puede haber desaparecido, la parte if
no puede ser un bloque vacío.
Si f(n) es O(g(n)), entonces podemos tomar O(g(n)) como la cota superior del tiempo de
ejecución de la sentencia condicional.
La razón de esto es que:
1. Podemos despreciar el O(1) de la condición,
2. Si se ejecuta la parte else, sabemos que g(n) es una cota para ella, y
3. Si se ejecuta la parte if en lugar de la else, el tiempo de ejecución será O(g(n))
porque f(n) es O(g(n)).
Similarmente si g(n) es O(f(n)), podemos acotar el tiempo de ejecución de la sentencia
condicional por O(f(n)). Cuando la parte else no existe, g(n) es 0, el tiempo es O(f(n)).
El problema se da cuando ni f(n) es O(g(n)), ni g(n) es O(f(n)). Se sabe que una de las
dos partes, nunca las dos, se ejecutará, y por tanto una cota de salvaguarda para el tiempo
de ejecución es tomar el mayor entre f(n) y g(n), lo cual se traduce en tomar como tiempo
de ejecución de las sentencias condicionales a O(max(f(n), g(n))).

Bloques
Si O(f1(n)), O(f2(n)), ... O(fk(n)) son las cotas superiores de las sentencias dentro del
bloque, entonces O(f1(n) + f2(n) + ... + fk(n)) será una cota superior para el tiempo de
ejecución del bloque completo. Cuando sea posible se puede emplear la regla de la suma
para simplificar esta expresión.

Llamadas a procedimientos.
La complejidad de llamar a un procedimiento viene dada por la complejidad del contenido
del procedimiento en sí. El costo de llamar es una constante que se puede omitir.

Ejercicio: Calcular la complejidad en notación O de los siguientes fragmentos de código,


explicando que regla para el cálculo de complejidad así como que reglas para cada tipo
de instrucción utilizó.
1.- for (int i= 0; i < n; i++)
2.- for (int i= 0; i < k; i++) { j=a+b;}
3.- for (int i= 0; i < n; i++) {
for (int j= 0; j < n; j++) {
x*=j;
}
}
4.- if A[1,1] = 0 then
for i = 1 to n do
for j = 1 to n do
A[i,j] = 0
else
for i = 1 to n do
A[i,i] = 1
5.- for (int i= 0; i < n; i++) {
for (int j= 0; j < k; j++) {
a=b;
}
}

Вам также может понравиться