En la lección anterior aprendiste a elegir estructura con criterio; en esta vamos a mirar atrás con calma. Repasaremos el curso completo contándolo como lo que realmente ha sido: la historia de TaskFlow, una aplicación que empezó siendo un simple diccionario de Python y terminó con tablero, deshacer, notificaciones, índices, jerarquías y un planificador de dependencias. Ver el camino entero de una vez fija el mapa mental mejor que cualquier lista de definiciones, y el test de autoevaluación final te dirá con honestidad qué llevas bien atado y qué conviene repasar antes de los proyectos.
Contenido
- La evolución de TaskFlow, módulo a módulo
- Tabla-chuleta: todo lo que hemos construido
- Los cinco conceptos transversales
- Qué sabes hacer ahora que no sabías al empezar
- Autoevaluación tipo test
La evolución de TaskFlow, módulo a módulo
flowchart LR
V0[v0.1<br/>tarea = dict] --> V1[M1-M2<br/>tablero<br/>listas] --> V2[M3<br/>deshacer<br/>pilas] --> V3[M4<br/>notificaciones<br/>colas y heaps]
V3 --> V4[M5<br/>índices<br/>hash] --> V5[M6<br/>jerarquías<br/>árboles] --> V6[M7<br/>planificador<br/>grafos] --> V7[M8<br/>criterio<br/>y proyectos]
Módulo 1 — Los cimientos: TDA, Big O y memoria
TaskFlow nació como un dict humilde: {"id": 1, "titulo": "...", "prioridad": 2, "estado": "pendiente"}. Antes de construir nada aprendimos a pensar: la diferencia entre el contrato de un TDA y su implementación, la notación Big O de O(1) a O(n²), la tabla de costes de list/dict/set, y cómo medir de verdad con timeit. También miramos la memoria de frente: un array es contigüidad pura (base + i × tamaño, y de ahí el acceso O(1)), la list de Python es un array dinámico, y insert(0)/pop(0) esconden un O(n) que perseguiríamos durante todo el curso.
Módulo 2 — El tablero: listas enlazadas
Primer problema real: insertar y borrar tareas del tablero sin pagar desplazamientos. Con Nodo y ListaEnlazada construimos el tablero de TaskFlow y aceptamos el trato de las listas enlazadas: inserción O(1) en cabeza a cambio de acceso O(n) por posición. NodoDoble y ListaDoblementeEnlazada dieron el HistorialTareas navegable en ambos sentidos, y la ListaCircular el RepartidorTareas round-robin. De propina, los clásicos: invertir la lista, detectar ciclos con Floyd (liebre y tortuga), fusionar ordenadas e insertar_ordenado.
Módulo 3 — Deshacer: pilas
"Ctrl+Z" es la operación estrella de cualquier editor, y su estructura es la pila: lo último que hiciste es lo primero que se deshace. Sobre Pila y PilaEnlazada montamos el HistorialAcciones y el GestorDeshacerRehacer con dos pilas (deshacer empuja en rehacer, y viceversa). Vimos que la pila también valida (filtro_balanceado), calcula (evaluar_postfija, infija_a_postfija), recuerda extremos (PilaConMinimo) y sostiene la propia ejecución del programa: la pila de llamadas explica la recursión, el RecursionError y cómo convertir recursión en iteración con una pila explícita.
Módulo 4 — Notificaciones: colas
Las notificaciones exigían justicia FIFO: Cola sobre ListaEnlazada dio la ColaNotificaciones, y por el camino demostramos que dos pilas hacen una cola (ColaConDosPilas). La ColaCircular (ring buffer) resolvió el RegistroEventos con memoria fija. Cuando "por orden de llegada" dejó de bastar, llegaron las colas de prioridad: de la versión ingenua a heapq con tuplas (prioridad, contador, tarea) en la BandejaUrgencias. Y collections.deque demostró ser la navaja suiza de los extremos: HistorialConLimite con maxlen, VentanaProductividad de media móvil y el deque monótono.
Módulo 5 — Índices: tablas hash
Buscar una tarea por id recorriendo listas era O(n); la magia O(1) del dict dejó de ser magia cuando construimos una tabla hash desde cero: TablaHashIngenua, hash polinómico, colisiones por encadenamiento, factor de carga 0.75 y rehashing en TablaHash. En la práctica: claves hashables, defaultdict, Counter, el índice invertido etiqueta→ids del BuscadorEtiquetas, memoización y two-sum. Y el límite honesto que abrió el siguiente módulo: el hash no conoce el orden ni entiende de rangos.
Módulo 6 — Jerarquías: árboles
Los proyectos contienen tareas que contienen subtareas: jerarquía pura. NodoArbol modeló el árbol general y presupuestar mostró el poder del postorden (los hijos antes que el padre). Con NodoBinario llegaron los tipos de árbol, la representación en array (hijos en 2i+1/2i+2) y los cuatro recorridos, también iterativos con pila. El ArbolBusqueda (ABB) resolvió por fin "prioridad entre 1 y 3" con rango, el ArbolAVL garantizó O(log n) con rotaciones, los árboles B/B+ explicaron los índices de las bases de datos, y el Monticulo propio (flotar/hundir) reconstruyó heapq por dentro para la BandejaUrgencias 2.0. Clásicos: es_abb, reconstruir, top_k.
Módulo 7 — Dependencias: grafos
"La tarea B no puede empezar hasta acabar la A" no es jerarquía: es un grafo. La clase Grafo (lista de adyacencia como dict de dicts, con la convención "arista A→B = B depende de A") modeló el DAG de TaskFlow. BFS con deque, DFS recursivo e iterativo, hay_ciclo con colores blanco/gris/negro y el orden_topologico de Kahn ordenaron el trabajo; componentes_conexas agrupó; dijkstra, bellman_ford y la mención a Floyd-Warshall midieron caminos; prim, kruskal y UnionFind tejieron redes mínimas. El broche: el planificador completo con niveles paralelos y ruta_critica, más sugerir_colaboradores, pagerank y los grafos implícitos en rejilla.
Módulo 8 — El criterio
Este módulo: el método de las siete preguntas, el árbol de decisión, la gran tabla, y los proyectos que vienen.
Tabla-chuleta: todo lo que hemos construido
Tu índice de referencia rápida. Si algún nombre no te evoca inmediatamente su idea, esa es la lección a repasar.
| Identificador | Módulo | Qué hace |
|---|---|---|
Nodo / ListaEnlazada |
2 | Lista enlazada simple; el primer tablero de TaskFlow |
NodoDoble / ListaDoblementeEnlazada |
2 | Enlaces en ambos sentidos; base del HistorialTareas |
ListaCircular / RepartidorTareas |
2 | Round-robin: repartir tareas por turnos |
Pila / PilaEnlazada |
3 | LIFO sobre list y sobre nodos |
HistorialAcciones / GestorDeshacerRehacer |
3 | Deshacer/rehacer con dos pilas |
filtro_balanceado, evaluar_postfija, infija_a_postfija |
3 | Validación y evaluación de expresiones con pila |
PilaConMinimo |
3 | Mínimo en O(1) con pila auxiliar |
Cola / ColaNotificaciones |
4 | FIFO sobre lista enlazada |
ColaConDosPilas |
4 | Una cola construida con dos pilas |
ColaCircular / RegistroEventos |
4 | Ring buffer de memoria fija |
BandejaUrgencias |
4 y 6 | Cola de prioridad con heapq; 2.0 con montículo propio |
HistorialConLimite / VentanaProductividad |
4 | deque con maxlen y media móvil |
TablaHashIngenua / TablaHash |
5 | Hash desde cero: encadenamiento, carga 0.75, rehashing |
BuscadorEtiquetas |
5 | Índice invertido etiqueta→ids |
NodoArbol / presupuestar |
6 | Árbol general de proyectos; agregado en postorden |
NodoBinario |
6 | Árbol binario y recorridos |
ArbolBusqueda (+ rango) |
6 | ABB con consulta por rangos |
ArbolAVL |
6 | ABB autoequilibrado: O(log n) garantizado |
Monticulo |
6 | Montículo binario propio: flotar/hundir |
es_abb, reconstruir, top_k |
6 | Clásicos de árboles |
Grafo |
7 | Lista de adyacencia (dict de dicts) |
hay_ciclo, orden_topologico |
7 | Colores blanco/gris/negro; algoritmo de Kahn |
dijkstra, bellman_ford |
7 | Caminos mínimos (sin/con pesos negativos) |
prim, kruskal, UnionFind |
7 | Árboles de expansión mínima |
ruta_critica, planificador por niveles |
7 | Planificación del DAG de TaskFlow |
NucleoTaskFlow |
8 | Combinación dict + índices + montículo |
Los cinco conceptos transversales
Más importantes que cualquier estructura concreta, porque son los que seguirás usando cuando aparezcan estructuras que este curso no cubre:
- TDA frente a implementación. La pila es un contrato (push/pop/peek);
list,PilaEnlazadao undequeson formas de cumplirlo. Programa contra el contrato y podrás cambiar la implementación sin tocar el resto del código. - Big O como idioma. No es matemática decorativa: es la forma de predecir si un código que funciona con 100 elementos sobrevivirá a 100 000. La diferencia entre
insobrelist(O(n)) y sobreset(O(1)) decide si tu bucle es lineal o cuadrático. - Recursión ↔ iteración. Toda recursión es una pila implícita; toda recursión se puede reescribir con una pila explícita (y a veces conviene, recuerda
RecursionError). Recorridos de árboles y DFS son el mismo patrón con dos trajes. - Estructuras que construyen estructuras. La cola sobre lista enlazada, la cola con dos pilas, la pila con mínimo, el montículo sobre array, el grafo sobre dict de dicts, la caché LRU con dict + lista doble... Componer es la habilidad; memorizar, solo el atajo.
- Medir en vez de suponer.
timeitfue la primera herramienta del curso y debe ser la última palabra en cualquier discusión de rendimiento. La teoría orienta; los datos reales deciden.
Qué sabes hacer ahora que no sabías al empezar
Lista honesta: todo esto lo has hecho, no solo leído.
- Leer y escribir análisis Big O de tu propio código, y verificar la teoría con
timeit. - Implementar desde cero, en Python, listas enlazadas (simple, doble, circular), pilas, colas, ring buffers, tablas hash con colisiones y rehashing, ABB, AVL, montículos y grafos.
- Elegir entre
list,dict,set,dequeyheapqsabiendo qué hay debajo y qué cuesta cada operación. - Reconocer patrones: LIFO → pila, FIFO → cola, "el más urgente" → montículo, clave exacta → hash, rangos → árbol, dependencias → grafo.
- Aplicar los algoritmos clásicos: Floyd, recorridos de árboles, BFS/DFS, detección de ciclos, orden topológico, Dijkstra, Bellman-Ford, Prim, Kruskal.
- Combinar estructuras coordinadas para cumplir varios requisitos de coste a la vez.
- Y lo que aún no sabes (ordenación en profundidad, programación dinámica, tries...): lo tienes localizado, y la siguiente lección te da el mapa para llegar.
Errores Comunes y Consejos
- Confundir "me suena" con "lo sé". El test de abajo es útil solo si lo haces sin mirar; el repaso pasivo produce una falsa sensación de dominio.
- Repasar estructuras como fichas aisladas. Repasa problemas: "¿cómo haría el deshacer?", "¿cómo detectaría el ciclo?". La estructura debe venir a ti desde el problema, no al revés.
- Saltarse los porqués. Saber que el
dictes O(1) sin recordar el hash polinómico y el rehashing es conocimiento frágil: se cae en la primera pregunta de seguimiento de una entrevista. - No reimplementar de memoria. Antes de los proyectos, prueba a reescribir sin mirar una
Pila, unaColaCirculary el BFS. Donde te atasques, ahí está tu hueco real.
Ejercicios
Autoevaluación tipo test. Responde sin consultar las lecciones y luego contrasta con las soluciones razonadas.
- En una
listde Python con 1 000 000 de elementos, ¿cuál de estas operaciones es la más cara? (a)lista[500000](b)lista.append(x)(c)lista.insert(0, x)(d)lista.pop() - El
GestorDeshacerRehacerusa dos pilas. Al ejecutar "deshacer", ¿qué ocurre exactamente? - ¿Por qué la
BandejaUrgenciasencola tuplas(prioridad, contador, tarea)y no(prioridad, tarea)? - Un compañero guarda los ids de tareas completadas en una
listy compruebaid in completadasdentro de un bucle sobre 50 000 tareas. ¿Cuál es el coste total y cómo lo arreglas? - ¿Qué estructura responde "dame todas las tareas con prioridad entre 1 y 3" en O(log n + k), y por qué un
dictno puede? - Insertas en un ABB las claves 1, 2, 3, 4, 5, en ese orden. ¿Qué forma tiene el árbol y qué coste tiene ahora buscar el 5? ¿Qué estructura lo evita?
- ¿Qué recorrido usó
presupuestarpara sumar el coste de proyectos → tareas → subtareas, y por qué precisamente ese? - En el DAG de dependencias de TaskFlow, ¿qué algoritmo te da un orden válido de ejecución y qué señal te avisa de que no existe tal orden?
- ¿Verdadero o falso? "Un montículo mantiene todos sus elementos completamente ordenados."
- ¿Qué dos estructuras combinaba la caché de "modo repaso" (ejercicio 3 de la lección anterior) y qué aporta cada una?
Soluciones
- (c)
insert(0, x)es O(n): desplaza el millón de elementos una posición. (a) es O(1) por contigüidad (base + i × tamaño); (b) y (d) son O(1) (amortizado en el append) porque tocan solo el final. - Se hace
popde la pila de deshacer (la acción más reciente), se revierte su efecto y esa acción se hacepushen la pila de rehacer. Si después el usuario ejecuta una acción nueva, la pila de rehacer se vacía: la historia alternativa deja de ser válida. - Dos razones: el contador desempata prioridades iguales garantizando orden de llegada (FIFO dentro de la misma prioridad) y, además, evita que
heapqintente comparar los dicts de tarea entre sí (losdictno son comparables y lanzaríaTypeError). insobrelistes O(n); dentro del bucle, O(n·m) — con 50 000 tareas, del orden de miles de millones de comparaciones. Conviertescompletadasensetuna sola vez (O(m)) y cada consulta pasa a O(1): el total queda en O(n + m).- Un ABB/AVL (o una lista ordenada con
bisect): desciendes hasta el arranque del rango en O(log n) y recorres en inorden los k resultados. Eldictno puede porque la función hash dispersa a propósito: destruye toda relación de orden entre claves para lograr el O(1) por clave exacta. - Degenera en una "lista" inclinada a la derecha: cada clave es mayor que la anterior, así que todos cuelgan del hijo derecho. Buscar el 5 cuesta O(n). Lo evita el AVL, que rota al detectar desequilibrio y garantiza altura O(log n) llegue como llegue la entrada.
- Postorden: procesa los hijos antes que el padre, de modo que cuando toca calcular el presupuesto de un nodo, los presupuestos de todos sus subárboles ya están calculados y basta sumarlos.
- El orden topológico (Kahn: ir extrayendo nodos con grado de entrada 0). Si el algoritmo termina sin haber procesado todos los vértices —o si el DFS de colores encuentra una arista hacia un nodo gris—, hay un ciclo de dependencias y no existe orden válido.
- Falso. El montículo solo garantiza la propiedad de montículo: cada padre ≤ sus hijos (en un min-heap). El mínimo está en la raíz, pero los hermanos no guardan orden entre sí; por eso insertar y extraer son O(log n) y no O(n) — es un orden "suficiente", no total.
- Un
dictid→nodo (localizar en O(1)) y una lista doblemente enlazada con el orden de uso (mover un nodo a cabeza y expulsar por la cola en O(1)). El dict no sabe de orden; la lista no sabe buscar: juntas, todas las operaciones son O(1).
Interpretación: 9-10 aciertos, listo para los proyectos; 6-8, repasa las lecciones de las preguntas falladas; menos de 6, vuelve a los ejercicios de los módulos flojos antes de seguir — los proyectos finales asumen todo esto.
Conclusión
TaskFlow ha pasado de un dict suelto a un sistema con siete familias de estructuras trabajando coordinadas, y tú has pasado de usar list "porque sí" a justificar cada elección con operaciones y costes. El mapa está completo: los cimientos (módulo 1), las estructuras lineales (2-4), los índices (5), las jerarquías (6), las redes (7) y el criterio (8). En la siguiente lección te dejo la biblioteca: los recursos con los que seguir creciendo por tu cuenta cuando este curso termine.
Curso de Estructuras de Datos
Módulo 1: Introducción a las Estructuras de Datos
- ¿Qué son las Estructuras de Datos?
- Importancia de las Estructuras de Datos en la Programación
- Tipos de Estructuras de Datos
- Complejidad Algorítmica y Notación Big O
- Arrays y Memoria: la Base de las Estructuras de Datos
Módulo 2: Listas
- Introducción a las Listas
- Listas Enlazadas
- Listas Doblemente Enlazadas
- Listas Circulares
- Ejercicios con Listas
Módulo 3: Pilas
- Introducción a las Pilas
- Operaciones Básicas con Pilas
- Implementación de Pilas
- Aplicaciones de las Pilas
- Ejercicios con Pilas
Módulo 4: Colas
- Introducción a las Colas
- Operaciones Básicas con Colas
- Colas Circulares
- Colas de Prioridad
- Colas Dobles (Deques)
- Ejercicios con Colas
Módulo 5: Tablas Hash y Diccionarios
- Introducción a las Tablas Hash
- Funciones Hash y Resolución de Colisiones
- Diccionarios y Conjuntos en la Práctica
- Ejercicios con Tablas Hash
Módulo 6: Árboles
- Introducción a los Árboles
- Árboles Binarios
- Recorridos de Árboles
- Árboles Binarios de Búsqueda
- Árboles AVL
- Árboles B
- Montículos (Heaps)
- Ejercicios con Árboles
Módulo 7: Grafos
- Introducción a los Grafos
- Representación de Grafos
- Algoritmos de Búsqueda en Grafos
- Algoritmos de Caminos Mínimos
- Árboles de Expansión Mínima
- Aplicaciones de los Grafos
- Ejercicios con Grafos
