Bienvenido al curso de Estructuras de Datos. En esta primera lección vamos a responder la pregunta fundamental que da nombre al curso: ¿qué es exactamente una estructura de datos? Entender bien esta definición —y la diferencia entre lo que una estructura promete hacer y cómo lo hace por dentro— te dará el marco mental sobre el que se apoya todo lo demás. Además, conocerás TaskFlow, la aplicación de gestión de tareas que construiremos pieza a pieza a lo largo del curso y que nos servirá de hilo conductor para que cada concepto tenga una aplicación real desde el primer día.
Contenido
- Definición formal: datos, relaciones y operaciones
- Analogías cotidianas para entenderlo mejor
- Tipo Abstracto de Datos (TDA) vs implementación concreta
- Presentando TaskFlow: nuestro proyecto del curso
- Las necesidades de datos de TaskFlow
Definición formal: datos, relaciones y operaciones
Una estructura de datos es una forma de organizar información en la memoria de un ordenador para poder trabajar con ella de manera eficaz. Pero esta frase, aunque correcta, se queda corta. La definición formal tiene tres componentes, y los tres son igual de importantes:
- Datos: los valores que queremos almacenar (números, textos, objetos...).
- Relaciones: cómo se conectan esos valores entre sí (uno detrás de otro, en jerarquía, en red...).
- Operaciones: qué acciones podemos realizar sobre ellos (insertar, buscar, borrar, recorrer...).
Podemos resumirlo en una fórmula informal:
Veamos qué significa cada componente con un ejemplo mínimo en Python:
# Datos: tres títulos de tareas
tareas = ["Diseñar logo", "Escribir informe", "Revisar presupuesto"]
# Relaciones: el orden importa. "Diseñar logo" va ANTES que "Escribir informe".
# La estructura (una lista) mantiene esa relación de orden por nosotros.
# Operaciones: la estructura define qué podemos hacer
tareas.append("Enviar factura") # insertar al final
primera = tareas[0] # acceder por posición
tareas.remove("Escribir informe") # eliminar un elemento
print(tareas)
# ['Diseñar logo', 'Revisar presupuesto', 'Enviar factura']Analicemos el ejemplo línea a línea:
tareas = [...]crea la estructura y almacena los datos (tres cadenas de texto).- La relación aquí es el orden secuencial: cada tarea tiene una posición, y esa posición significa algo (por ejemplo, prioridad de ejecución). Si el orden no importara, quizá otra estructura sería más adecuada.
append, el acceso con[0]yremoveson las operaciones que esta estructura nos ofrece. Cada estructura de datos ofrece un catálogo distinto de operaciones, y ahí está la clave de todo el curso: elegir la estructura cuyas relaciones y operaciones encajen con tu problema.
Un mismo conjunto de datos puede organizarse de muchas maneras. Los títulos de esas tres tareas podrían guardarse en una secuencia ordenada, en un conjunto sin orden, o asociados cada uno a un identificador. Los datos son los mismos; lo que cambia son las relaciones y las operaciones, es decir, la estructura.
Idea clave: una estructura de datos no es "un sitio donde meter cosas", sino un contrato: define qué relaciones se mantienen entre los datos y qué operaciones puedes hacer con ellos.
Analogías cotidianas para entenderlo mejor
Las estructuras de datos existen desde mucho antes que los ordenadores. Los humanos llevamos siglos organizando información física, y esas organizaciones cotidianas son analogías perfectas:
| Objeto cotidiano | Cómo organiza la información | Qué facilita | Qué dificulta |
|---|---|---|---|
| Lista de la compra | Elementos uno detrás de otro | Añadir al final, leer en orden | Encontrar un producto concreto entre cientos |
| Pila de platos | El último que dejas es el primero que coges | Apilar y desapilar por arriba | Coger el plato de abajo sin desmontar todo |
| Cola del supermercado | El primero que llega es el primero atendido | Atender por orden de llegada | Colarse (¡y está mal visto!) |
| Agenda telefónica por letra | Cada nombre bajo su inicial | Saltar directamente a la "M" | Listar contactos por fecha de alta |
| Árbol genealógico | Jerarquía de padres e hijos | Ver ascendencia y descendencia | Relacionar primos lejanos directamente |
| Mapa de carreteras | Ciudades conectadas por vías | Encontrar rutas entre puntos | No hay un "orden" único de ciudades |
Fíjate en un detalle importante de la tabla: cada organización facilita unas cosas y dificulta otras. La pila de platos es comodísima para lo que hace una pila (dejar y coger por arriba), pero terrible si necesitas el plato del fondo. No existe la organización perfecta para todo; existe la organización adecuada para cada uso. Este principio, que en la cocina es obvio, es exactamente el mismo en programación.
Cada fila de esa tabla corresponde, además, a una estructura de datos real que estudiaremos en este curso: la lista, la pila, la cola, la tabla hash, el árbol y el grafo. De momento quédate solo con la intuición; el panorama completo de tipos lo veremos en la lección 01-03.
Tipo Abstracto de Datos (TDA) vs implementación concreta
Este es el concepto más importante de la lección, y uno que distingue a un desarrollador que "usa" estructuras de uno que las entiende.
Un Tipo Abstracto de Datos (TDA) es la especificación de una estructura: define qué operaciones ofrece y cómo se comportan, sin decir nada de cómo están programadas por dentro. La implementación concreta es el código real que cumple esa especificación.
La analogía clásica es el coche:
- TDA: "un coche tiene un volante para girar, un acelerador para ir más rápido y un freno para parar". Cualquier persona que sepa conducir puede usar cualquier coche, porque el contrato es el mismo.
- Implementación: un coche concreto puede ser de gasolina, eléctrico o híbrido. Por dentro son radicalmente distintos, pero el volante, el acelerador y el freno se comportan igual.
Veámoslo en código. Definamos el TDA "Pila" (lo estudiaremos a fondo en el módulo 3; aquí solo nos interesa como ejemplo de contrato):
TDA Pila:
- apilar(elemento): añade un elemento a la cima
- desapilar(): quita y devuelve el elemento de la cima
- cima(): consulta el elemento de la cima sin quitarlo
- esta_vacia(): indica si no hay elementosObserva que la especificación no menciona listas de Python, ni memoria, ni punteros. Solo dice qué hace cada operación. Ahora, dos implementaciones concretas del mismo TDA:
class PilaConLista:
"""Implementación del TDA Pila usando una lista de Python."""
def __init__(self):
self._elementos = [] # el estado interno es una lista
def apilar(self, elemento):
self._elementos.append(elemento)
def desapilar(self):
return self._elementos.pop() # pop() quita el último elemento
def esta_vacia(self):
return len(self._elementos) == 0
class PilaConDiccionario:
"""Otra implementación del MISMO TDA, con un estado interno distinto."""
def __init__(self):
self._elementos = {} # el estado interno es un diccionario
self._contador = 0 # llevamos la cuenta de posiciones
def apilar(self, elemento):
self._elementos[self._contador] = elemento
self._contador += 1
def desapilar(self):
self._contador -= 1
return self._elementos.pop(self._contador)
def esta_vacia(self):
return self._contador == 0Y ahora, lo importante: el código que usa la pila no necesita saber cuál de las dos implementaciones tiene delante.
def procesar(pila):
"""Esta función funciona con CUALQUIER implementación del TDA Pila."""
pila.apilar("acción 1")
pila.apilar("acción 2")
while not pila.esta_vacia():
print("Deshaciendo:", pila.desapilar())
procesar(PilaConLista()) # Deshaciendo: acción 2 / acción 1
procesar(PilaConDiccionario()) # Deshaciendo: acción 2 / acción 1Explicación detallada del ejemplo:
procesarsolo conoce el contrato: sabe que existeapilar,desapilaryesta_vacia, y qué se supone que hacen. No mira dentro de la pila.- Las dos clases guardan sus datos de forma completamente distinta (una lista vs un diccionario con contador), pero desde fuera se comportan igual: ambas devuelven los elementos en orden inverso al que se apilaron.
- El guion bajo inicial en
self._elementoses una convención de Python que significa "esto es interno, no lo toques desde fuera". Refuerza la idea de que el usuario del TDA solo debe usar las operaciones públicas.
Esta separación tiene consecuencias prácticas enormes:
- Puedes cambiar la implementación sin romper el código que la usa (por ejemplo, por una más rápida cuando aprendas a medir rendimiento).
- Puedes razonar sobre tu programa en términos de contratos, sin cargar con los detalles internos de cada pieza.
- Es la base de cómo estudiaremos cada estructura del curso: primero el TDA (qué promete), después una o varias implementaciones (cómo lo cumple).
Presentando TaskFlow: nuestro proyecto del curso
A lo largo del curso vamos a construir, pieza a pieza, TaskFlow: una aplicación de gestión de tareas y proyectos escrita en Python. La idea es sencilla: en lugar de estudiar cada estructura de datos con ejemplos desconectados, cada una resolverá una necesidad real de la aplicación, de modo que al terminar el curso tendrás tanto los conocimientos como un proyecto tangible.
¿Qué hace TaskFlow? Lo que harías con cualquier gestor de tareas tipo Trello o Todoist, en versión simplificada:
- Crear tareas con título, descripción, prioridad y estado.
- Organizarlas en un tablero y moverlas entre estados ("pendiente", "en curso", "hecha").
- Deshacer la última acción si te equivocas.
- Recibir y procesar notificaciones.
- Buscar cualquier tarea al instante por su identificador.
- Clasificar tareas en categorías y subcategorías.
- Declarar que una tarea depende de otra y calcular en qué orden hacerlas.
Nuestra primera pieza de TaskFlow puede ser tan simple como esto:
# taskflow.py — versión 0.1: una tarea es un diccionario con sus atributos
tarea = {
"id": 1,
"titulo": "Preparar la demo del viernes",
"prioridad": "alta", # alta / media / baja
"estado": "pendiente", # pendiente / en curso / hecha
}
print(f"[{tarea['id']}] {tarea['titulo']} ({tarea['prioridad']})")
# [1] Preparar la demo del viernes (alta)Aquí un diccionario de Python agrupa los atributos de una sola tarea: cada clave ("id", "titulo"...) se asocia a un valor. Es la representación mínima con la que empezaremos; en el módulo 2 la haremos crecer.
Las necesidades de datos de TaskFlow
Si TaskFlow solo tuviera una tarea, no necesitaríamos este curso. El reto aparece cuando hay muchas tareas y muchas maneras de relacionarlas. Enumeremos las necesidades de datos de la aplicación, porque cada una anticipa un módulo del curso:
| Necesidad de TaskFlow | ¿Qué relación hay entre los datos? | Módulo donde la resolveremos |
|---|---|---|
| Un tablero con las tareas en orden | Secuencia: una tarea detrás de otra | Módulo 2 (listas) |
| Deshacer la última acción | Lo último hecho es lo primero en deshacerse | Módulo 3 (pilas) |
| Procesar notificaciones por orden de llegada | La primera en llegar es la primera en salir | Módulo 4 (colas) |
| Encontrar una tarea por su id al instante | Asociación identificador → tarea | Módulo 5 (tablas hash) |
| Categorías con subcategorías | Jerarquía: padres e hijos | Módulo 6 (árboles) |
| "La tarea B no puede empezar hasta acabar la A" | Red de dependencias entre tareas | Módulo 7 (grafos) |
Fíjate en que cada fila describe una relación distinta entre los mismos datos (tareas). Esa es exactamente la definición con la que abrimos la lección: los datos son los mismos, pero las relaciones que necesitamos mantener —y las operaciones que queremos hacer— cambian según el caso de uso. Por eso no existe "la mejor estructura de datos", sino la adecuada para cada necesidad.
Errores Comunes y Consejos
- Confundir los datos con la estructura. "Tengo una lista de clientes" mezcla dos cosas: los clientes (datos) y la lista (estructura elegida). Acostúmbrate a preguntarte: ¿qué relaciones necesito mantener y qué operaciones voy a hacer? Esa pregunta decide la estructura.
- Creer que en Python "todo se hace con listas y diccionarios". Es cierto que Python te da estructuras muy potentes de serie, y las usaremos, pero si no entiendes el TDA que hay detrás, no sabrás cuándo una
listes una mala elección (lo verás muy claro en la lección 01-02). - Saltarse la especificación e ir directo al código. Antes de implementar, escribe (aunque sea en un comentario) qué operaciones debe ofrecer tu estructura. Es el hábito TDA: primero el contrato, después el código.
- Consejo: crea ya una carpeta
taskflow/en tu equipo y guarda en ella los ejemplos del curso. Al final tendrás una aplicación completa construida por ti.
Ejercicios
Ejercicio 1: identificar los tres componentes
Para cada escenario, identifica los datos, las relaciones y al menos dos operaciones que necesitarías:
- El historial de páginas visitadas de un navegador web.
- Los asientos reservados de una sala de cine.
- Los comentarios y respuestas (a otros comentarios) de un vídeo.
Ejercicio 2: especificar un TDA
Escribe la especificación (solo el contrato, sin código) de un TDA ListaDeTareas para TaskFlow con estas capacidades: añadir una tarea, marcar una tarea como hecha, contar cuántas tareas quedan pendientes y obtener la siguiente tarea pendiente. Indica para cada operación qué recibe y qué devuelve.
Ejercicio 3: dos implementaciones, un contrato
Implementa en Python el TDA Contador con las operaciones incrementar(), decrementar() y valor(). Hazlo dos veces: una clase ContadorEntero que guarde internamente un número, y una clase ContadorLista que guarde internamente una lista a la que añade un elemento al incrementar y quita uno al decrementar. Comprueba que una función externa funciona igual con ambas.
Soluciones
Solución 1:
- Historial del navegador — Datos: las URL visitadas. Relación: orden temporal de visita (la más reciente "encima"). Operaciones: añadir la página actual, volver a la anterior, vaciar el historial.
- Asientos de cine — Datos: los asientos (fila y número) y su estado. Relación: cada asiento se identifica de forma única por su posición; no hay orden temporal relevante. Operaciones: consultar si un asiento está libre, reservarlo, liberarlo.
- Comentarios de un vídeo — Datos: los comentarios (autor, texto, fecha). Relación: jerárquica, cada respuesta "cuelga" de otro comentario. Operaciones: añadir comentario raíz, responder a un comentario, listar las respuestas de uno dado.
Solución 2:
TDA ListaDeTareas:
- anadir(titulo, prioridad): recibe el título y la prioridad de una tarea
nueva; la incorpora como pendiente. No devuelve nada.
- marcar_hecha(titulo): recibe el título de una tarea existente y cambia
su estado a "hecha". Devuelve True si la encontró, False si no.
- pendientes(): no recibe nada. Devuelve el número de tareas pendientes.
- siguiente(): no recibe nada. Devuelve la tarea pendiente más antigua,
o None si no queda ninguna.Lo importante no es la redacción exacta, sino que hayas descrito comportamiento sin mencionar cómo se guarda nada internamente.
Solución 3:
class ContadorEntero:
def __init__(self):
self._n = 0 # estado interno: un entero
def incrementar(self):
self._n += 1
def decrementar(self):
self._n -= 1
def valor(self):
return self._n
class ContadorLista:
def __init__(self):
self._marcas = [] # estado interno: una lista de marcas
def incrementar(self):
self._marcas.append(1) # añadimos una marca
def decrementar(self):
self._marcas.pop() # quitamos una marca
def valor(self):
return len(self._marcas) # el valor es cuántas marcas hay
def probar(contador):
contador.incrementar()
contador.incrementar()
contador.incrementar()
contador.decrementar()
print(contador.valor()) # debe imprimir 2 en ambos casos
probar(ContadorEntero()) # 2
probar(ContadorLista()) # 2Ambas clases cumplen el mismo contrato con estados internos distintos: es exactamente la diferencia entre TDA e implementación. (Nota: ContadorLista gasta más memoria; medir ese tipo de diferencias es justo lo que aprenderemos en las próximas lecciones.)
Conclusión
En esta lección has aprendido que una estructura de datos es la combinación de datos, relaciones y operaciones, y que conviene separar el TDA (el contrato: qué hace) de la implementación (el código: cómo lo hace). También has conocido TaskFlow, nuestra aplicación de gestión de tareas, y has visto que cada una de sus necesidades —tablero, deshacer, notificaciones, búsqueda, categorías, dependencias— exige mantener relaciones distintas entre los mismos datos.
Queda una pregunta en el aire: si varias estructuras pueden almacenar los mismos datos, ¿de verdad importa tanto cuál elijas? La respuesta es un rotundo sí, y en la siguiente lección lo comprobarás con números reales: verás cómo una mala elección puede hacer que TaskFlow tarde miles de veces más en hacer lo mismo.
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
