Analizar un algoritmo consiste en describir su comportamiento asintótico. La notación O-grande es una cota superior: f(n) = O(g(n)) si existen constantes positivas c y n₀ tales que 0 ≤ f(n) ≤ c·g(n) para toda n ≥ n₀. De forma dual, Ω acota por abajo y Θ acota por ambos lados (cota ajustada). Para resolver recurrencias de divide y vencerás se aplica el teorema maestro; junto a la recursión conviven los paradigmas de programación dinámica, voraz (greedy) y backtracking.
En ordenamiento y búsqueda, cualquier algoritmo basado en comparaciones tiene una cota inferior de Ω(n log n) en el peor caso. Merge sort corre en Θ(n log n) en todos los casos usando espacio auxiliar Θ(n); quicksort es Θ(n log n) en promedio pero O(n²) en el peor caso. La búsqueda binaria localiza un elemento en O(log n), pero exige que el arreglo esté previamente ordenado.
Para grafos, la lista de adyacencia ocupa Θ(V + E), mucho menos que la matriz de adyacencia Θ(V²) cuando el grafo es disperso. La suma de las longitudes de todas las listas es |E| en un grafo dirigido y 2|E| en uno no dirigido. La contrapartida: verificar si existe la arista (u,v) es O(1) con matriz, pero con lista cuesta O(grado(u)), hasta O(V) en el peor caso.
Sobre lista de adyacencia, tanto BFS como DFS recorren el grafo en tiempo lineal O(V + E). El algoritmo de Dijkstra (caminos mínimos con pesos no negativos) implementado con min-heap binario cuesta O((V + E) log V); con montículo de Fibonacci mejora a O(E + V log V). Estos costos, junto con los del árbol de expansión mínima (MST), permiten predecir el desempeño al leer código o pseudocódigo.
1. Según la definición formal de la notación O-grande, se dice que f(n) = O(g(n)) cuando existen constantes positivas c y n₀ tales que, para toda n ≥ n₀, se cumple la siguiente relación:
La definición formal de O-grande acota superiormente f(n) mediante c·g(n) para n suficientemente grande; la relación 0 ≤ c·g(n) ≤ f(n) corresponde en cambio a la cota inferior Ω. (CLRS, 'Introduction to Algorithms', 3a ed., Cap. 3 (Growth of Functions))
2. ¿Qué tipo de cota asintótica proporciona la notación Ω(g(n)) sobre el tiempo de ejecución de un algoritmo?
Ω(g(n)) define una cota inferior asintótica: garantiza que, salvo por una constante, el algoritmo no crece más lento que g(n); confundirla con la cota superior O es un error común. (CLRS, 'Introduction to Algorithms', 3a ed., Cap. 3 (Growth of Functions))
3. Cuando se afirma que un algoritmo tiene complejidad Θ(g(n)), esto significa que:
Θ(g(n)) exige simultáneamente una cota superior y una inferior con constantes distintas, es decir, un crecimiento asintóticamente ajustado; hablar solo de cota superior corresponde a O, no a Θ. (CLRS, 'Introduction to Algorithms', 3a ed., Cap. 3 (Growth of Functions))
4. Un programador necesita localizar un valor dentro de un arreglo de un millón de elementos que ya está ordenado de forma ascendente. Si aplica el algoritmo de búsqueda binaria en lugar de un recorrido secuencial, ¿cuál es la complejidad temporal en el peor caso de su búsqueda?
La búsqueda binaria divide a la mitad el espacio de búsqueda en cada paso, logrando O(log n) sobre un arreglo ordenado, muy inferior al O(n) de un recorrido secuencial. (CLRS, 'Introduction to Algorithms', 3a ed., Cap. 2 (ejercicio 2.3-5) / Cap. 12)
5. ¿Cuál es la cota inferior demostrada para el número de comparaciones que requiere, en el peor caso, cualquier algoritmo de ordenamiento basado en comparaciones (como quicksort o merge sort)?
El teorema de la cota inferior para ordenamiento por comparaciones demuestra que se requieren al menos Ω(n log n) comparaciones en el peor caso, límite que alcanzan merge sort y heapsort. (CLRS, 'Introduction to Algorithms', 3a ed., Cap. 8 (Teorema 8.1))
6. Al analizar una implementación de quicksort, un estudiante observa que el pivote elegido siempre resulta ser el elemento más pequeño del subarreglo, generando particiones de tamaño 1 y n−1 en cada llamada recursiva. ¿Qué complejidad temporal tendrá el algoritmo en este escenario?
Cuando el pivote siempre es el elemento mínimo, las particiones quedan desbalanceadas (1 y n−1), generando el peor caso de quicksort, O(n²); Θ(n log n) corresponde al caso promedio, no a este escenario. (CLRS, 'Introduction to Algorithms', 3a ed., Cap. 7 (Quicksort))
7. El algoritmo de ordenamiento por mezcla (merge sort) divide el arreglo recursivamente y combina los subarreglos ordenados. ¿Cuál es su complejidad temporal en el peor caso?
Merge sort mantiene Θ(n log n) en el mejor, promedio y peor caso porque siempre divide el arreglo a la mitad, independientemente del orden inicial de los datos. (CLRS, 'Introduction to Algorithms', 3a ed., Cap. 2 (sección 2.3, Merge Sort))
8. Un montículo binario (heap) de n elementos se construye a partir de un arreglo desordenado mediante el procedimiento Build-Max-Heap, y posteriormente se le insertan elementos uno por uno. ¿Cuál de las siguientes afirmaciones describe correctamente las complejidades de ambas operaciones?
Build-Max-Heap procesa todo el arreglo en O(n) aprovechando que la mayoría de los nodos están cerca de las hojas, mientras que cada inserción individual solo recorre la altura del árbol, O(log n). (CLRS, 'Introduction to Algorithms', 3a ed., Cap. 6 (Heapsort))
9. En una tabla hash que resuelve colisiones mediante encadenamiento (chaining), bajo la hipótesis de hashing uniforme simple, ¿cuál es el costo promedio de una búsqueda en términos del factor de carga α (número de elementos entre número de cubetas)?
Bajo hashing uniforme simple, el costo esperado de una búsqueda con encadenamiento es Θ(1 + α), donde el 1 corresponde al cálculo de la función hash y α a la longitud promedio de la cadena recorrida. (CLRS, 'Introduction to Algorithms', 3a ed., Cap. 11 (Teoremas 11.1 y 11.2, Hash Tables))
10. Un árbol binario de búsqueda se construyó insertando una secuencia de claves ya ordenadas de menor a mayor, sin ningún mecanismo de balanceo. ¿Cuál es la complejidad de una operación de búsqueda en este árbol resultante?
Insertar claves ya ordenadas sin balanceo produce un árbol degenerado equivalente a una lista enlazada, por lo que la búsqueda cuesta O(n) en vez del O(log n) típico de un árbol balanceado. (CLRS, 'Introduction to Algorithms', 3a ed., Cap. 12 (Binary Search Trees))
11. Un ingeniero debe elegir entre BFS y DFS para explorar un grafo representado por lista de adyacencia con el objetivo de detectar ciclos. Ambos recorridos completos tienen la misma complejidad asintótica en función de V y E. ¿Cuál es dicha complejidad, común a ambos algoritmos?
Tanto BFS como DFS, al recorrer un grafo por lista de adyacencia, visitan cada vértice y cada arista una única vez, resultando en Θ(V + E) para ambos algoritmos. (CLRS, 'Introduction to Algorithms', 3a ed., Cap. 22 (secciones 22.2-22.3, BFS y DFS))
12. Un desarrollador debe representar un grafo disperso (con pocas aristas en relación con el número de vértices, es decir E mucho menor que V²) para optimizar el uso de memoria. ¿Qué representación resulta más eficiente en espacio de almacenamiento?
La lista de adyacencia ocupa Θ(V + E), proporcional al número real de aristas, mientras que la matriz de adyacencia siempre ocupa Θ(V²) sin importar cuán disperso sea el grafo, por lo que resulta ineficiente para grafos con pocas aristas. (CLRS, 'Introduction to Algorithms', 3a ed., Cap. 22 (Elementary Graph Algorithms))
13. Para verificar si existe una arista específica (u, v) en un grafo, ¿cuál es la diferencia de complejidad entre usar una matriz de adyacencia y usar una lista de adyacencia?
Con matriz de adyacencia, consultar (u,v) es un acceso directo O(1); con lista de adyacencia hay que recorrer la lista de u, cuyo costo depende de su grado y puede llegar a O(V) en el peor caso. (CLRS, 'Introduction to Algorithms', 3a ed., Cap. 22 (comparación de representaciones de grafos))
14. Una pila (stack) es una estructura de datos lineal cuyo principio de acceso a los elementos se conoce como:
Una pila sigue el principio LIFO: el último elemento apilado es el primero en desapilarse, a diferencia de una cola (FIFO). (CLRS, 'Introduction to Algorithms', 3a ed., Cap. 10 (sección 10.1, Stacks and Queues))
15. En una cola (queue), el orden en que los elementos son atendidos sigue el principio:
En una cola, los elementos se atienden en el mismo orden en que llegaron (FIFO), a diferencia de la pila, donde se atiende primero el último en llegar. (CLRS, 'Introduction to Algorithms', 3a ed., Cap. 10 (sección 10.1, Stacks and Queues))
16. ¿Cuál es la complejidad temporal de las operaciones push y pop sobre una pila implementada correctamente (ya sea con arreglo o con lista enlazada)?
Push y pop solo manipulan el elemento en la cima de la pila (agregar o quitar), sin recorrer el resto de la estructura, por lo que su costo es constante, O(1). (CLRS, 'Introduction to Algorithms', 3a ed., Cap. 10 (sección 10.1, Stacks and Queues))
17. ¿Cuál es la complejidad temporal de las operaciones enqueue y dequeue sobre una cola implementada correctamente?
Enqueue y dequeue únicamente actualizan los apuntadores de frente y final de la cola, sin recorrer los demás elementos, por lo que se ejecutan en O(1). (CLRS, 'Introduction to Algorithms', 3a ed., Cap. 10 (sección 10.1, Stacks and Queues))
18. Un compilador necesita verificar si las llaves, paréntesis y corchetes de un programa fuente están correctamente balanceados (cada símbolo de apertura tiene su correspondiente cierre en el orden adecuado). ¿Qué estructura de datos es la más adecuada para resolver este problema de manera eficiente?
Al apilar cada símbolo de apertura y desapilarlo cuando aparece su cierre correspondiente, la pila permite verificar el balance respetando el orden de anidamiento, algo que una cola no logra por su orden FIFO. (CLRS, 'Introduction to Algorithms', 3a ed., Cap. 10 (sección 10.1, aplicación clásica de pilas))
19. Durante la implementación de un algoritmo de recorrido en anchura (BFS) sobre un grafo representado por lista de adyacencia, se necesita almacenar temporalmente los vértices descubiertos que aún no han sido visitados, respetando el orden en que fueron encontrados. ¿Qué estructura de datos utiliza típicamente BFS para este propósito?
BFS emplea una cola para mantener el orden en que los vértices fueron descubiertos y garantizar que se visiten por niveles de distancia creciente. (CLRS, 'Introduction to Algorithms', 3a ed., Cap. 22 (sección 22.2, BFS))
20. Cuando una función se invoca a sí misma de manera recursiva, el entorno de ejecución guarda la información de cada llamada activa (variables locales, dirección de retorno) en una estructura de datos que se gestiona automáticamente. ¿Qué estructura de datos es esta y qué principio de acceso sigue?
El entorno de ejecución guarda cada llamada activa en una pila de llamadas: la última función invocada es la primera en terminar y ser retirada, principio LIFO. (CLRS, 'Introduction to Algorithms', 3a ed., Cap. 10 (sección 10.1, pila de llamadas y recursión))
21. Al comparar la inserción de un nuevo elemento al inicio de la estructura, ¿cuál es la diferencia de complejidad entre un arreglo y una lista simplemente enlazada?
Insertar al inicio de un arreglo obliga a desplazar todos los elementos existentes, O(n), mientras que en una lista enlazada basta con actualizar el apuntador al nuevo primer nodo, O(1). (CLRS, 'Introduction to Algorithms', 3a ed., Cap. 10 (sección 10.2, Linked Lists))
22. ¿Cuál es la complejidad temporal de acceder a un elemento en una posición arbitraria (por índice) dentro de un arreglo estático?
Un arreglo estático permite calcular la dirección de memoria de cualquier posición mediante aritmética de apuntadores, por lo que el acceso por índice es O(1). (CLRS, 'Introduction to Algorithms', 3a ed., Cap. 10 (sección 10.1, Arrays))
23. ¿Cuál es la complejidad temporal de acceder al elemento que ocupa la k-ésima posición dentro de una lista simplemente enlazada, partiendo del primer nodo?
Una lista enlazada no ofrece acceso directo por posición: para llegar al k-ésimo nodo hay que recorrer los apuntadores desde el primero, costo O(n). (CLRS, 'Introduction to Algorithms', 3a ed., Cap. 10 (sección 10.2, Linked Lists))
24. ¿Cuál de las siguientes operaciones sobre una pila (stack) NO se ejecuta en tiempo O(1)?
Push, pop y peek/top solo manipulan la cima de la pila en O(1); buscar un valor arbitrario exige recorrer potencialmente todos los elementos, costo O(n). (CLRS, 'Introduction to Algorithms', 3a ed., Cap. 10 (sección 10.1, Stacks and Queues))
25. En una lista doblemente enlazada, un proceso ya cuenta con un apuntador directo al nodo que desea eliminar (no necesita buscarlo desde el inicio). ¿Cuál es la complejidad de la operación de eliminación en este caso, comparada con eliminar un nodo del cual solo se conoce su valor y hay que localizarlo primero?
En una lista doblemente enlazada, si ya se tiene el apuntador al nodo, eliminarlo solo exige reconectar sus vecinos, O(1); si solo se conoce el valor, primero hay que recorrer la lista para localizarlo, O(n). (CLRS, 'Introduction to Algorithms', 3a ed., Cap. 10 (sección 10.2, listas doblemente enlazadas))
26. Una cola se implementa utilizando un arreglo de tamaño fijo con dos índices (frente y final) que solo avanzan hacia adelante sin reutilizar los espacios liberados al hacer dequeue. Después de varias operaciones de encolado y desencolado, el índice final llega al límite del arreglo aunque existan espacios libres al principio. ¿Qué técnica de implementación resuelve este problema de desperdicio de espacio?
Una cola circular reutiliza los espacios liberados al hacer avanzar los índices de frente y final mediante aritmética modular, evitando el desperdicio de espacio de una cola lineal con arreglo. (CLRS, 'Introduction to Algorithms', 3a ed., Cap. 10 (sección 10.1, implementación de colas con arreglo circular))
27. ¿Cuál es la definición formal de la notación asintótica f(n) = O(g(n))?
La notación O-grande exige una cota superior con una constante multiplicativa c y un umbral n₀; la segunda opción corresponde a Ω y la tercera a Θ. (CLRS, "Introduction to Algorithms", 3a ed., Cap. 3 (Growth of Functions))
28. ¿Cuál es la forma general de una recurrencia de divide y vencerás a la que puede aplicarse el teorema maestro?
El teorema maestro se aplica a recurrencias T(n) = aT(n/b) + f(n) con a ≥ 1 subproblemas y un factor de reducción b > 1; la primera opción corresponde a una recurrencia tipo Fibonacci y la segunda reduce por resta, no por división. (CLRS, "Introduction to Algorithms", 3a ed., Cap. 4 (Divide-and-Conquer, Teorema 4.1))
29. La recurrencia del algoritmo de ordenamiento por mezcla (merge sort) es T(n) = 2T(n/2) + Θ(n). Aplicando el teorema maestro, ¿cuál es la solución de esta recurrencia?
Con a=2, b=2, f(n)=Θ(n)=Θ(n^(log_2 2)), se cumple el caso 2 del teorema maestro, cuya solución es Θ(n log n), coincidente con la complejidad conocida de merge sort. (CLRS, "Introduction to Algorithms", 3a ed., Cap. 2 (sección 2.3) y Cap. 4 (Teorema 4.1))
30. La búsqueda binaria recursiva sobre un arreglo ordenado se describe mediante la recurrencia T(n) = T(n/2) + O(1). ¿Cuál es la solución de esta recurrencia según el teorema maestro?
Con a=1, b=2, f(n)=O(1)=Θ(n^0), se cumple el caso 2 del teorema maestro, que da T(n) = Θ(log n), consistente con el costo conocido de la búsqueda binaria. (CLRS, "Introduction to Algorithms", 3a ed., Cap. 2 (ejercicio 2.3-5) y Cap. 4 (Teorema 4.1))
31. Dada la recurrencia T(n) = 4T(n/2) + n, ¿qué caso del teorema maestro aplica y cuál es la solución asintótica correcta?
Con a=4, b=2, n^(log_2 4)=n²; como f(n)=n es polinomialmente menor que n², aplica el caso 1 y T(n)=Θ(n²); las demás opciones aplican el caso equivocado o confunden el exponente. (CLRS, "Introduction to Algorithms", 3a ed., Cap. 4 (Teorema 4.1, caso 1))
32. ¿Cuál de las siguientes recurrencias NO puede resolverse de forma directa aplicando el teorema maestro, debido a que f(n) no es polinomialmente comparable con n^(log_b a)?
En T(n) = 2T(n/2) + n/log n, f(n)=n/log n cae en la brecha entre los casos 1 y 2 (no es O(n^(1−ε)) ni Θ(n)), por lo que el teorema maestro no aplica directamente; las demás recurrencias corresponden limpiamente a los casos 2, 3 y 1. (CLRS, "Introduction to Algorithms", 3a ed., Cap. 4 (brecha entre los casos del teorema maestro))
33. El algoritmo de ordenamiento por mezcla (merge sort), cuya recurrencia es T(n) = 2T(n/2) + Θ(n), requiere memoria auxiliar de:
Merge sort necesita un arreglo auxiliar para la mezcla, lo que implica un espacio adicional Θ(n), además de su tiempo Θ(n log n). (CLRS, "Introduction to Algorithms", 3a ed., Cap. 2 (sección 2.3, Merge Sort))
34. Al ordenar con quicksort un arreglo que ya está ordenado, usando siempre el último elemento como pivote, la partición resulta lo más desbalanceada posible en cada llamada, generando la recurrencia T(n) = T(n − 1) + Θ(n). ¿Cuál es la complejidad resultante?
La recurrencia T(n) = T(n − 1) + Θ(n) se resuelve por suma aritmética y da Θ(n²), que coincide con el peor caso documentado de quicksort. (CLRS, "Introduction to Algorithms", 3a ed., Cap. 7 (Quicksort, análisis del peor caso))
35. ¿Cuál es la cota inferior demostrada, en el peor caso, para cualquier algoritmo de ordenamiento basado en comparaciones?
Mediante un argumento de árbol de decisión se demuestra que todo algoritmo de ordenamiento por comparaciones requiere Ω(n log n) comparaciones en el peor caso. (CLRS, "Introduction to Algorithms", 3a ed., Cap. 8 (Teorema 8.1))