Está en la página 1de 14

OBJETOS Y ABSTRACCIÓN DE DATOS

DOCENTE:
Christian Torres Morán

CONTENIDO A TRATAR HOY:


Big O.
Periodo: Mayo - Septiembre 2022
RETROALIMENTACIÓN:
Interrogantes de la clase anterior, debe contestar estas preguntas con sus
propias palabras y ejemplos, subirlos al aula virtual en el repositorio de
retroalimentación:
1. ¿Cuál es el propósito de un algoritmo computacional?
2. ¿Cuáles son los recursos más importante que utilizan los algoritmos?
3. ¿Qué puede entorpecer a la hora de medir la eficiencia de un algoritmo?
4. ¿Cómo se puede crear una métrica para analizar un algoritmo?
5. ¿Cuándo se aplica logaritmo en una expresión analizada?
6. ¿Cuándo se aplica una expresión mediante sumatoria en un análisis?
7. ¿Cuándo se aplica el mejor y el peor caso esperado?
8. ¿Qué es una notación asintótica?
OBJETIVOS DE LA CLASE:

✔ ENTENDER COMO ANALIZAR LA CALIDAD DE UN ALGORITMO BASÁNDOSE EN LA TÉCNICA


BIG O DE COMPLEJIDAD COMPUTACIONAL
ANÁLISIS DE ALGORITMOS BIG O
El Big O (Orden de crecimiento) es un lenguaje y métrica que se utiliza para describir la eficiencia de un algoritmo, utiliza unidades de medida
basadas en las operaciones primitivas, como sumar, restar, leer un registro, entre otros, para establecer una cantidad constante de tiempo c,
expresada de forma polinómica y utilizando notación asintótica las categoriza para definir su eficiencia.
la mejor forma de analizar la eficiencia de un algoritmo es graficando su
comportamiento en el plano cartesiano, como la gráfica del lado izquierdo,
donde, al incrementar la cantidad de datos de entrada, se puede verificar si el
tiempo de procesamiento también se incrementa o se mantiene, observe que el
gráfico muestra la eficiencia según la tabla de eficiencia:
EFICIENCIA DE UN ALGORITMO BASADO EN TIEMPOS DE
EJECUCIÓN
1 Entrada con valores constantes (Más eficiente)
Log n Resultados logarítmicos
n Resultado con tendencia lineal
N log n Logarítmico lineal
2
n Cuadrático
3
N Cúbico
2n Exponencial (Menos eficiente)
la notación asintótica describe los límites de una función cuando sus parámetros
tienden a un valor particular o al infinito.
Recuerde que la notación asintótica es usada en general para cualquier tipo de
función matemática. En este caso es útil ya que para cada una de las soluciones
encontradas sean capaces de proveer una función de costo del tipo T(n).
Existen tres posibles casos en el que se puede orientar el análisis de un algoritmo: “El peor de los casos” descrito como el límite superior y
denotado como O(big-O), “El mejor de los casos” descrito como el límite inferior y denotado como Ω (omega) y por último “El rango o caso
promedio” que incluye ambos límites, el inferior y el superior, denotado como Θ (theta).
ANÁLISIS DE ALGORITMOS BIG O
Notación de resultados:
Es importante entender como se puede expresar el resultado de un análisis ya que en algún momento se lo puede encontrar de la siguiente forma:  "
complejidad de tal algoritmo es O(2n)", en que puedes estar seguro de que se trata del peor de los casos.  O "la complejidad de tal algoritmo
es Θ(log n)", significa que dicho algoritmo nunca tendrá una complejidad mejor a Ω(log n) ni peor a O(log n).
Aunque existen otras notaciones asintóticas demás O, Θ y Ω como la o y la ω la notación de Big-O (O-notation) es la más utilizada. 
Por lo tanto para expresar las complejidades ordenadas desde la más eficiente hasta la menos eficiente se podría hacerlo de la siguiente forma:
O(1)
O (log n)
O(n)
O(n
log n)
O(n2)
O(n3)
O(2n)
O(n!)
Este cuadro describe la cantidad de tiempos de procesamiento considerando que Complejidad es la expresión calculable del esfuerzo requerido
por los recursos de procesamiento y n es la cantidad de datos de entrada, y los diferentes resultados serían el número de unidades de tiempo de
proceso que tendría el algoritmo analizado.
Este cuadro le podría dar una idea del enorme esfuerzo de una computadora que es capaz de realizar 10 9 operaciones primitivas por segundo.
Imagine el algoritmo de búsqueda analizado en la clase anterior, para realizar una búsqueda en una lista de 1 millón de elementos la entrada
de datos se expresaría así (n=106), recuerde que la complejidad del algoritmo fue O(n2), esto significa que tomaría al rededor de 16 minutos
devolver un resultado, el segundo algoritmo su complejidad fue O(n), y por lo tanto le tomaría 1 milisegundo en devolver un resultado. Ahora
imagine que la longitud de la lista fuera de 1 billón de elementos (n=109), la primera función devolvería el resultado en más de 31 años mientras
que en la segunda función sólo le tomaría 1 segundo. LA COMPLEJIDAD COMPUTACIONAL ES IMPORTANTE.
DEFINICIÓN DE COMPLEJIDAD Y SU MEDIDA
Para explicar como la notación Big O ayuda a analizar las complejidad de dos alternativa de desarrollo que permiten encontrar el mayor y el
menor de todos los números de un arreglo se utilizará el siguiente ejemplo:
En esta instrucción hay 1 asignación de complejidad
Si se modifica el algoritmo de tal forma que con un
int max = Integer.MAX_VALUE; for each se obtiene el menor y con otro for each se
En esta instrucción hay 1 asignación de complejidad obtiene el mayor se tendría:
int min = Integer.MIN_VALUE;
for(x=0 ; x < n; x++){ En este for() hay 2n + 2 de complejidad int max = Integer.MAX_VALUE;
if(arreglo[x] < nim) min = arreglo[x]; Considerando el peor caso tendría 2n int min = Integer.MIN_VALUE;
if(arreglo[x] > max) max = arreglo[x]; for(x: arreglo){
Considerando el peor caso tendría 2n
} if(x < nim) min = x;
}
= 1 asignación + 1 asignación + 2n + 2 del for + 2n + 2n de comparación y asignación
= 1+ 1 + 2n + 2 + 2n+ 2n
for(x: arreglo){
= 4 + 6n 🡪esta sería la expresión polinómica de la complejidad if(x > max) max = X;
= 4 + 6n 🡪Al aplicar simplificación asintótica quedaría O(n) }
Se podría pensar que en el primer ejemplo con un for
Si se aplica un poco más de simplicidad y estética en éste algoritmo se podría each se obtuvo O(n) con 2 for each en este ejemplo el
utilizar el for each de la siguiente forma: resultado sería O(2n) pero en realidad no es así, ya que
se tendría el mismo O(n) por la simplificación asintótica
int max = Integer.MAX_VALUE; Sin importar los procesos internos del for each el
int min = Integer.MIN_VALUE; resultado también sería igual al for tradicional en
for(x: arreglo){ cuanto a su complejidad algorítmica expresado en
if(x < min) min = x; notación Big O().🡪 O(n)
if(x > max) max = X;
}
EXPRESIÓN DE COMPLEJIDAD BIG O()
Los siguientes ejemplos de notaciones Big O le permitirán seleccionar los casos más complejos para expresar la complejidad de un algoritmo :

O(N+N2) Esta expresión le podría indicar que existe un for() y seguido de otros dos for() anidados, para tener una expresión en Big O se
selecciona el término más dominante o dicho de otra forma el peor caso posible de toda la expresión en este caso O(N2)
O(N + log N) Para esta expresión se debe selecciona la más dominante, considerando el análisis realizado hasta ahora y tomando como
referencia la gráfica en el plano cartesiano el log N no tienden a crecer en tiempos de procesamiento al incrementar los datos de entrada, pero
la tendencia lineal N tiende a crecer más que la logarítmica, por lo tanto el peor caso posible de toda la expresión en este caso es O(N)
O(5*2N+N100) Para concluir cual es la notación Big O de esta expresión se debe analizar los elementos internos, la expresión 5*2N se podría
indicar que es 2N por simplificación asintótica, esta expresión indica que al tener N como una potencia de crecimiento (datos de entrada), el
incremento de este valor significará también el incremento del tiempo de proceso, mientras que la expresión N100 utiliza la contante 100 para
indicar que, sin importar el valor de N siempre será potencia 100 veces que se realizará dichos procesos, por lo tanto se selecciona el término
más dominante o dicho de otra forma, el peor caso posible de esta expresión es O(2N)
Para ejemplificar estas notaciones, primero considere el siguiente proceso para mostrar el contenido de 2 arreglos:
Observe que se tienen 2 arreglos y por lo Observe que se tienen 2 arreglos y por lo
for(int a: arregloA){ tanto 2 recorridos, al tratarse de for() se for(int a: arregloA){ tanto 2 recorridos anidados, al tratarse de
podría indicar que la complejidad tendría 2 for() se podría indicar que la
System.out.println(a); una relación O(N) como el ejercicio anterior for(int b: arregloB){ complejidad tendría una relación O(N2) o
} pero no es así, ya que son dos recorridos System.out.println(a + b); O(N*N) como ya se ha analizado en
for(int b: arregloB){ diferentes por ser dos arreglos diferentes } ejercicios anteriores con for anidados pero
así que, la notación del Big O quedaría así: no es así, ya que son dos recorridos
System.out.println(b); O(A+B), ya que A representa el primer
} diferentes por ser dos arreglos diferentes
} recorrido y B el segundo recorrido o así que, la notación del Big O quedaría así:
proceso. O(A*B), ya que A representa el primer
recorrido y B el segundo recorrido o
¿Cuál de los dos procesos tendría mayor peso? 🡪O(A*B) proceso anidado.
EXPRESIÓN DE COMPLEJIDAD BIG O()
Para ejemplificar la diferencia de un crecimiento lineal O(N) y un crecimiento logarítmico O(log N) considere los ejemplos de búsqueda secuencial y
binaria
En la búsqueda secuencial, se dará el peor de los casos cuando tiene que recorrer toda la estructura por ejemplo que no exista el elemento a
buscar, donde el número de procesos a realizar para encontrar el elemento será igual al número de elementos O(N)

En la búsqueda binaria para su funcionamiento requiere que el arreglo este ordenado, funciona dividiendo el arreglo en mitades cada vez más
pequeñas, es decir, se ubica en cada mitad y compara, si el elemento buscado en mayor buscará a la derecha caso contrario buscará a la
izquierda de forma sucesiva hasta encontrar el elemento, por lo tanto se fracciona de forma logarítmica O(log N), por ejemplo, suponga que
desea encontrar el valor 9:

Verifica si lo encontró, si no es así, buscará a la derecha o a la izquierda (9 >


11)

Cada búsqueda se realiza siempre


a la mitad de forma logarítmica Verifica si lo encontró, si no es así, buscará a la derecha o a la izquierda (9 > 5)

 
Verifica si lo encontró, si no es así, buscará a la derecha o a la izquierda (9 >
x
2 =n 6)

Log2n = x
Lo encontró
DEFINICIÓN DE COMPLEJIDAD Y SU MEDIDA BIG O()
Otro ejemplo para aplicar una notación lineal consistiría en el llamado de una función recursiva que encuentra el factorial de un número:
Suponiendo que se desea encontrar el factorial de 5, se aplicaría la siguiente instrucción: factorial(5)
public int factorial (int n){ Llamadas:
1 5 * factorial(4) Observe que el llamado es lineal, note que el total de
if (n==0){ llamadas es igual a n + 1, por lo que al aplicar la
2 4 * factorial(3)
return 1; } 3 complejidad sería O(n), una vez que se aplica la
3 * factorial(2)
else { 4 2 * factorial(1)
simplificación asintótica
return (n * factorial(n-1)); } 5 1 * factorial(0)
}
6 1

Otro ejemplo para aplicar una notación exponencial consistiría en el llamado doble de una función recursiva que encuentra el valor de una
posición de la secuencia de Fibonacci:
Suponiendo que se desea analizar la función recursiva de la serie de Fibonacci a diferencia de la anterior realiza doble llamado, por ejemplo
si se llama F(4):
Llamadas:
public int F(int n){ 1 🡪20 F(4)
if(n <= 1){ 2 🡪21 F(3) F(3)
return 1; } 3 🡪22 F(2) F(2) F(2) F(2)
return F(n-1) + F(n-1);
4 🡪23 F(1) F(1) F(1) F(1) F(1) F(1) F(1) F(1)
}
Observe que el llamado es exponencial, note que el total de llamadas es igual a n, pero por cada llamada de incrementa el doble por lo
que al aplicar la complejidad sería O(2n), esto demuestra que este tipo de recursividad reduce la codificación pero demanda de mayor
esfuerzo computacional
DEFINICIÓN DE COMPLEJIDAD Y SU MEDIDA BIG O()
¿cómo desarrollo un análisis Big O()?
Recuerde que un análisis Big O() se basa en la aplicación de la simplificación asintótica seleccionando la carga más pesada o el peor caso posible,
ya que no esta centrada en las expresiones polinómicas como tal sino el la estimación de mayor esfuerzo computacional, por ejemplo, se analizará
los siguientes códigos sin importar su utilidad:

public void foo(int[] arreglo){ public void foo(int[][] arreglo){


int suma = 0;
int producto = 1; for (int i = 0; i < arreglo.length; i++){
for (int j = 0; j < arreglo.length;
for (int i = 0; i < arreglo.length; i++){ i++){
suma += arreglo[i]; System.out.println(arreglo[i][j]);
} }
}
for (int i = 0; i < arreglo.length; i++){ }Esta función tiene 2 for() anidados sobre un
producto *= arreglo[i]; arreglo bidimensional o matriz, como se trata
} de for anidados es decir un for() dentro de
} otro for() la complejidad sería N*N
quedando así O(N2)
Esta función tiene 2 for() sobre el mismo
arreglo, ambos realizan acumulaciones, el
primero mediante sumatoria y la segundo
mediante multiplicaciones, se podría pensar
que la complejidad sería 2N pero siempre
se aplica la simplificación asintótica
quedando así O(N)
DEFINICIÓN DE COMPLEJIDAD Y SU MEDIDA BIG O()
¿cómo desarrollo un análisis Big O()?
Recuerde que un análisis Big O() se basa en la aplicación de la simplificación asintótica seleccionando la carga más pesada o el peor caso posible,
ya que no esta centrada en las expresiones polinómicas como tal sino el la estimación de mayor esfuerzo computacional, por ejemplo, se analizará
los siguientes códigos sin importar su utilidad:

public void foo(int[] arreglo){ public void foo(int[] arregloA, int[] arregloB){

for (int i = 0; i < arreglo.length; i++){ for (int i = 0; i < arregloA.length; i++){
for (int j = i+1; j < arreglo.length; i++){ for (int j = 0; j < arregloB.length; i++){
System.out.println(arreglo[i]+arreglo[j]); int suma = arregloA[i] + arregloB[j];
} }
} }
} }
Esta función tiene 2 for() anidados sobre un arreglo Esta función tiene 2 for() anidados sobre dos arreglos
unidimensional o vector, como se trata de for unidimensionales o vectores diferentes, como se trata de
anidados y a pesar de que el segundo siempre for anidados se podría pensar que la complejidad Big O
empieza en la posición siguiente del recorrido del sería N*N o N2 pero no es así, ya que son vectores
primero (En forma de triángulo), la complejidad Big diferentes quedando así O(A*B) o O(AB)
O sería N*N quedando así O(N2)
CONCLUSIONES DEL BIG O
La notación del Big O utiliza la expresión que refleje la mayor carga (tendencia hacia el peor caso) de cualquier expresión de
cálculo para definir su complejidad computacional, no identifica el cálculo ya que su expresión esta basada en la identificación
de mayor tendencia en el plano cartesiano y su disposición de crecimiento al infinito tomando los datos de entrada.
Con estas expresiones se puede definir cuan eficiente en un algoritmo, por ejemplo dependiendo de la expresión se podría indicar que:

O(1) < O(log n) < O(n) < O(n log n) < O(n2) < O(nn) < O(n!)

Esta notación y la gráfica en el plano cartesiano muestran según cada Big


O cuando un algoritmo es más eficiente que otro, recuerde que para
encontrar la complejidad computacional se basa en tiempos de proceso,
donde cada algoritmo genera una expresión, que al simplificarla
mediantes reglas asintóticas se obtienen estas notaciones.
Para obtener estas expresiones asintóticas se aplica la regla de ignorar
constantes y seleccionar el término más dominante:

5 + (15 * 20) = O(1) 🡪Como son valores constantes se representan con 1


5n = O(n) 🡪Ignora las constantes y selecciona el termino dominante
3n + 15 * 7 = O(n) 🡪Ignora las constantes y selecciona el termino dominante
3n2 + 5n + 7 = O(n2) 🡪Selecciona el termino dominante después de ignorar las constantes
CONCLUSIONES DEL BIG O
Recuerde que la notación Big O esta basada en analizar el crecimiento del tiempo de proceso al incrementar los datos de entrada es decir
muestra la tendencia basado en el peor de los casos.

Big O(1) no importa el valor que se encuentra entre paréntesis esta notación
representa el crecimiento que tendrá una entrada constante, se la considera la
más eficiente porque sin importar cuantos procesos se realicen siempre será la
misma carga de trabajo
Big O(n) Esta notación representa el crecimiento lineal o de tiempo de proceso
que tendrá un algoritmo, significa que a mediada crecen los datos de entrada
2
también crecerá el tiempo de proceso para Seresolver
obtieneel: O(n
algoritmo.
)
Por ejemplo, si se analiza de manera bien detallada el siguiente código se tendría el
siguiente resultado:
2n+2 tiempos de proceso
for (m = 0; m < cantidad; m++){
SumaActual+= inta[m]; 2 tiempos de proceso
if (SumaActual > MaximaSuma){ 1 tiempos de proceso
MaximaSuma =
1 tiempos de proceso 6*n
SumaActual;
else if(SumaActual < 0) 1 tiempos de proceso = 2n + 2 + 6n
SumaActual = 0; = 8n +2
1 tiempos de proceso
}
= 8n +2 🡪si aplicamos simplificación asintótica se obtiene O(n)
CONCLUSIONES DEL BIG O
Big O(n2) Esta notación representa el crecimiento de la complejidad computacional al cuadrado de las entradas que tomará un algoritmo para
procesar las instrucciones, esto quiere decir que, a mediada crecen los datos de entrada su tiempo de incrementa al cuadrado de los mismos.

for (n = 0; n < cantidad; n++){ 2n+2 tiempos de proceso = 2n + 2 + n + (2n+2)*n + n*n + n*n +
SumaActual = 0; n*n
n veces del for() externo
for (m = n; m < cantidad; m++){ = 2n + 2 + n + 2n2 + 2n + n2 + n2 + n2
SumaActual = (int)a[m]; (2n+2)*n veces del for() externo = 2n + 2 + n + 2n2 + 2n + 3n2
if(SumaActual < MaximaSuma) = 5n2 + 5n + 2
n*n veces del for() interno por las veces de for() el externo Al aplicar la simplificación asintótica
MaximaSuma = SumaActual;
n*n veces del for() interno por las veces de for() el externo =5n2 + 5n + 2
}
} n*n veces del for() interno por las veces de for() el externo Se obtiene: O(n2)
Big O(n3) Esta notación representa el crecimiento de la complejidad computacional al cubo de las entradas que tomará un algoritmo para procesar
las instrucciones, es decir, a mediada crecen los datos de entrada su tiempo de procesamiento se incrementará al cubo de los mismos, por ejemplo:
= 2n + 2 + (2n+2)*n + n*n+ (2n+2)*n*n + n*n*n + n*n + n*n
2n+2 tiempos de proceso = 2n + 2 + 2n2 + 2n + n2 + (2n+2)n2 + n3 + n2+ n2
for (n = 0; n < cantidad; n++){
(2n+2)*n veces del for() externo = 2n + 2 + 2n2 + 2n + n2 + 2n3 + 2n2 + n3 + n2+ n2
for (m = 0; m < cantidad; m++){
= 3n3 + 7n2 + 4n + 2
SumaActual = 0; n*n veces del for() intermedio y externo
for (o = n; o < m; o++){ Al aplicar la simplificación asintótica
(2n+2)*n*n veces del for() intermedio y del for() el externo
SumaActual = (int)a[o];
} n*n*n veces del for() interno, intermedio y del externo 3n3 + 7n2 + 4n +
if(SumaActual < MaximaSuma) 2
n*n veces del for() intermedio y del for() externo Se obtiene: O(n3)
MaximaSuma =
SumaActual; n*n veces del for() intermedio y del for() externo
}

También podría gustarte