Evaluación de complejidad temporal con Big-O
Clase 13 de 18 • Curso de Complejidad Algorítmica con JavaScript
Contenido del curso
Clase 13 de 18 • Curso de Complejidad Algorítmica con JavaScript
Contenido del curso
Marcelo Arias
Yohan Olmedo Bello Trejos
Markin Piero Pulache Guarniz
Alan Vazquez
Rubén Ernesto Aragón Gil
Juan Esteban Galvis
Marcelo Arias
Santiago Puerta
Arturo Casas Quiroga
Carlos Rodríguez
Carlos Rodríguez
Gregory Hernandez Abad
Henry J. Perez
Anfernee Valera
Marcelo Arias
Luis E. Gama Ramirez
Marcelo Arias
Frandel Corporan Rodríguez
Irving Juárez
Cristian Blandon
Irving Juárez
León Sergio Mora Guerrero
José Eduardo Vinagre de Dios
Juan Sanchez
Marcelo Arias
Jesus Manuel Hernandez Diaz
Carlos Mazzaroli
Marcelo Arias
Rubén Padilla
Bryan Castano
Luz Amanda Quilindo Celis
Luis Fernando Cortes Duque
Matias Barboza
Leonardo Buezo
📂 Genial, ahora puedes practicar con más algoritmos desde la carpeta algorithms del repositorio del curso.
🙏 Te invito a hacerlo, pues con esto empleas las reglas para hallar la notación Big-O de varios algoritmos que podrás emplear en entrevistas técnicas.
Genial aporte profe
buen aporte profe
Solo como pequeña nota que no cambia el los resultados.
Estrictamente hablando. Hay algunos procesos que no se suman ya que están dentro de un for por lo tanto no se ejecutan una unica vez, se ejecutan por cada iteración
.
Buble Sort
1 + n( n(1 + 1 + 1 + 1) ) + 1n(4n) + 24n^2 + 2O(n^2) // Solo se toma el exponente mas grande
.Selection Sort
1 + n( 1 + n(1 + 1) + 1 + 1 + 1 + 1 ) + 1n( 2n + 5 ) + 22n^2 + 5n + 2O(n^2) // Solo se toma el exponente mas grande
.Genial tu ejemplo
Siempre se habla del for o while pero si usamos los métodos de los arreglos (map, filter, reduce, etc.) también valdrían O(n) ?
😁 Sí, exacto, dado que map, filter y reduce iteran sobre un arreglo y repiten instrucciones sobre n entrada, también cuentan como O(n). Lo mismo sucederá con cualquier otro método que use una iteración, por ejemplo, ForEach.
Bueno... la verdad no estoy muy seguro de lo que dice @360macky ya que los algoritmos internos que ofrece python se encuentran optimizados de algunas maneras, puede que la funciones internas tengan complejidades de On(logN) o incluso O logn. No todo lo que sea un bucle es O(n) hay que tener esto en cuenta.
Al fin entiendo el ordenamiento por burbuja! Además de aprender del ordenamiento lineal y de selección :D
Forma de analizar la notación big-o en complegidad temporal
Constantes, variables y condicionales
const entrada = input(); // O(1) const x = 5; // O(1) if entrada == "holi" { // O(1) print("saludo" * x) // O(1) }
Ciclos o iteraciones
// Donde "n" es la entrada y "c" es una constante for (let i = 1; i <= n; i += c) { // O(n) // Cualquier sentencia O(1) } for (let i = n; i > 0; i -= c) { // O(n) // Cualquier sentencia O(1) }
Ciclos anidados
// Donde "n" es la entrada y "c" es una constante for (let i = 1; i <= n; i += c) { for (let j = 1; j <= n; j += c) { // O(n²) // Cualquier sentencia O(1) } } for (let i = n; i > 0; i -= c) { for (let j = 1; j <= n; j += c) { // O(n²) // Cualquier sentencia O(1) } }
Ciclos con incremento extenso
// Donde "n" es la entrada y "c" es una constante for (let i = 1; i <= n; i *= c) { // O(log[n]) // Cualquier sentencia O(1) } for (let i = n; i > 0; i /= c) { // O(log[n]) // Cualquier sentencia O(1) }
Ciclos con incremento exponencial
// Donde "n" es la entrada y "c" es una constante for (let i = 2; i <= n; i = pow(i, c)) { // O(log[log[n]]) // Cualquier sentencia O(1) } for (let i = n; i > 1; i = func(i)) { // O(log[log[n]]) // Cualquier sentencia O(1) }
Ojo, un bloque de tipo bucle dentro de otro bloque es una multiplicación, y bloques diferentes es una adición! O(n *1 *1 + 1) -> O(n)
Para los casos de un for de crecimiento logarítmico. Se aceptan correcciones.
for(let i = 0; i < n; i -= 2) { // O(log N) // .... } for(let i = 0; i < n; i += 2) { // O(log N) // .... } for(let i = 0; i < n; i *= 2) { // O(log N) // .... } for(let i = 0; i < n; i /= 2) { // O(log N) // ... } for(let i = 0; i < n; i^3) { // O(log(log N)) // ... }
Vale, entonces no es tanto la cantidad de pasos que se toman, es decir si afectan por supuesto, pero no es la forma en que calificaríamos un algoritmo respecto su desenpeño. _ Sino que esta relacionado a la repeticion y lo anidados que los pasos del algoritmo segun el tamaño de el input.
Puedo saber cual seria el valor en Big-O de una función recursiva? Pienso que sería O(n), pero no estoy seguro
😁 ¡Hola Anfer! Gran pregunta. Dependerá del algoritmo:
Una función iterativa (que usa for o while) puede convertirse en una función recursiva, y viceversa siempre. Un for en un algoritmo iterativo para Suma, no es tan diferente a un return suma(n-1) de un algoritmo recursivo de Suma.
function suma(n) { if (n <= 0) { return 0; } else { return 1 + suma(n-1); } }
✅ Podríamos concluir que un ciclo for que suele estar representado como O(n), también puede ser una función recursiva. Y también interpretarse como O(n). Pero...
⛔ Eso no significa que todos los algoritmos recursivos sean O(n). Como este caso:
function AlgoritmoBigOFactorial(n) { for (let i = 0; i < n; i++) { AlgoritmoBigOFactorial(n-1); } }
Esto es O(n!), un algoritmo con una complejidad elevada, y también es una función recursiva.
💡 Entonces, dependerá del algoritmo.
Pero algo para comentar: los algoritmos recursivos no son muy eficientes. Hay que controlar que el stack de la memoria no colapse, y al hacer muchas más operaciones, el tiempo también es afectado.
Sin embargo, eso no quita que sean súper interesantes, y legibles para resolver problemas (como árboles binarios).
En el Algoritmo de ordenamiento por burbuja, la complejidad temporal del Big - o es N^2 + 6 o solo N^2 ?
¡Hola Luis! Cuando simplificamos a Big-O sólo quedaría N^2, porque podemos descartar +6.
El por qué de esto, es que N^2 es un grado mayor a +6.
😊 Te dejo otras reglas para simplificar una notación Big-O:
!Simplificación de la notación
Creo que usando ciclos for e if statements es buena idea para poder evaluar lo complejidad de cada uno de los algoritmos. Sin embargo, se pueden hacer mucho mas cortos con algunos de los métodos que JS nos ofrece por defecto.
.
Ejemplo con el algoritmo de linearSearch
function linearSearch(arr, value){ return arr.find(item => item === value) } const arr = [1,2,3,4,5,6,7,8,9,10]; console.log( linearSearch(arr, 8) ) // 8
Creo que para lo que se quiere hacer en la clase que es evaluar la complejidad del código y no las líneas que se generan de código, es mucho mejor con for e if statements.
Si Cristian, pero en tus proyectos usarías puros ciclos for?
Les dejo este enlace para revisar cómo la complejidad llega a casos como logaritmos o factoriales. https://careerkarma.com/blog/big-o-notation-time/
En esta versión del algoritmo de ordenamiento de burbuja se puede optimizar un poco, sin embargo la complejidad sigue siendo la misma. Es interesante ver que aunque el algorimo optimizado hace menos comparaciones la complejidad sigue siendo la misma. Rapidamente explico que comparaciones estan demás, el algoritmo despues de la primera iteración del for externo ya tiene el elemento mas grande en la ultima posición del arreglo, por lo que en la siguiente interación ya no es necesario comparar este valor, después de la segunda iteración, ya no es necesario comparar los ultimos dos valores, y así con las demás iteraciones, por lo que se considera el peor escenario que es n y por eso se sigue condiderando como cuadratico su complejidad.
También hay que notar que al hacer longitud = arreglo.length al intentar acceder a la posición arreglo[j+1] provocara un error cuando j sea igual a logitud -1. Supongamos que para corregir esto hacemos que j < longitud -1, entonces, ¿la complejidad seria O(n-1)??. No se la respuesta, lo que se es que es irrelevante ya que por simplicidad se quita el -1.
Y que hay del caso cuando el arreglo ya esta ordenado, existe una optimización para este caso y me parece que la complejidad es O(2n) lo que es lo mismo que O(n), sin embargo la complejidad del algoritmo sigue siendo cuadratico, porque se toma el peor escenario.
Tambien la optimización sirve para arreglos semi-ordenados, pero a este punto ya sabemos la historia, el algoritmo sigue siendo cuadratico, pero no significa que por eso no podemos hacer optimizaciones. En cuanto a crecimiento, que es lo que evalua la complejidad O(100n) es lo mismo que O(n) pero en ejecucion de un programa yo prefiero un O(2n) que un O(20n) por que este ultimo tarda 10 veces más.
No me crean mucho, cuestionen y nunca paren de aprender
Como seria para evaluar un algoritmo en caso de que sea una notacion logaritmica?
¡Hola Juancho!
La notación O(n) es asignada a los bucles en toda la función. Dado que es lineal O(n), también abarca un crecimiento de complejidad logarítmico O(log(n)).
--
Pero si específicamente necesitas encontrar un crecimiento más cercano al logarítmico, una estrategia que pudes aplicar es verificar si la cantidad de iteraciones del bucle tiende a disminuir considerablemente cuando el algoritmo funciona.
Por ejemplo, en una búsqueda binaria, la cantidad de números donde se busca el número objetivo, empieza a disminuir en cada iteración del bucle. El tamaño de la entrada sigue consumiendo el recurso del tiempo, pero al requerir menos iteraciones (y por lo tanto requerir menos pasos), puede calificar como Big O (log(n)) o notación Big-O logarítmica.
function busquedaBinaria(arreglo, objetivo) { let izquierda = 0; // O(1) let derecha = arreglo.length - 1; // O(1) while (izquierda <= derecha) { // O(n) 👈👉 O(log(n)) let medio = Math.floor((izquierda + derecha) / 2); // O(1) if (arreglo[medio] === objetivo) { return medio; // O(1) } else if (arreglo[medio] < objetivo) { izquierda = medio + 1; // O(1) } else { derecha = medio - 1; // O(1) } } return -1; // O(1) } busquedaBinaria([1, 2, 3, 4], 4)
Y cuando se usa una notacion cuadratica O(n²)?
un .push seria O(n) u O(1)?
¡Hola Carlos!
Un Array.prototype.push() sería un O(1). Pues sólo añade 1 elemento al final de una lista, y el algoritmo no realiza otras acciones.
Hola Carlos!
Depende de cuantos elementos vayas agregando al array. En el caso de JS los arrays son creados con una capacidad x, mientras agregas elementos en ese límite es O(1), pero cuando sobrepasas la capacidad inicial (x) el array debe crecer dinámicamente para poder almacenar más elementos. Esta operación sería O(n) por que debe copiar todos los elementos en su nuevo destino.
Saludos.
Habeis Fallado por el BubbleSort Analysis ,\n Si bien es Cierto BubbleSort es O ( n2 ) , hay que ver que en cada iteraccion del Outer_For se reduce el # Iteracciones del Inner_For as : n - i - 1 , lo que si que hace O ( n2 ) Pero con una reduccion lineal del Cliclo Interno , \nMientras que Selection Sort : Ese si que ese el Peor de todos los Algoritmos de Ordenamiento , el Repite Comparacciones n*n veces y no es Eficiente ps manda cada elemento a la posicioon inmediata superior sin tener en cuenta que si corresponde / pertencence A[ i,J ] en esa posicion, luego se vuelve a comparar al siguiente i y asi por todo el i_For , Insertion Sort es mucho mejor porque enfrenta este problema de excesivas comparaciones , Luego estan los >Algoritmos mejorados como HeapSort, QuickSort y MergeSOrt que implementan Recursion y si que lo hacen mejor por O( nlogn ) hasta O ( log n ) en el mejor de los casos. \n Los Algoritmos de Ordenamientos son >Fundamentales para Entender todo esto de <Complejidad />Espacio S && Tiempo T . \n printf("Nunca Pares de Aprender\n") ;
no puedo creer que este curso lleve escondido 2 años aqui..y no lo habia visto.. es muy bueno
yo tampoco creo que todo sea tan fácil, estoy seguro que hay matemáticas complejas en todo esto donde se podrán hacer demostraciones de que esa Big-O tiene esa complejidad, aun así estoy aprendiendo mucho.
Por supuesto, y de hecho hay toda una carrera de ingeniería específica para esto que no es Ingeniería en Sistemas ni tampoco Informática. Es la INGENIERIA EN COMPUTACION. Ahí también se abarca la computación cuántica.
Comparto otra manera de implementar búsqueda lineal, con un solo return, haciendo el código más legible como una buena práctica de código limpio: .
function linealSearch(arr, value) { let found = false; let key = -1; let index = 0; while(index = 0 < arr.length && found == false) { if(arr[index] == value) { found = true; key = index; } index++; } return key; }