En esta primera lección sentaremos las bases de todo el curso: qué es exactamente un algoritmo, qué propiedades debe cumplir para merecer ese nombre y de qué formas podemos expresarlo antes de escribir una sola línea de código. Entender bien estos fundamentos es esencial, porque el resto del curso —análisis de complejidad, estrategias de diseño, algoritmos clásicos y optimización— se construye sobre ellos. Además, presentaremos RutaBus, la aplicación ficticia de movilidad urbana que nos acompañará como caso práctico durante todo el curso.
Contenido
- RutaBus: el caso práctico del curso
- ¿Qué es un algoritmo (y qué no lo es)?
- Propiedades de un algoritmo
- Formas de expresar un algoritmo
- Del pseudocódigo al código Python: primer ejemplo en RutaBus
- Motivación: hay algoritmos mejores que otros
RutaBus: el caso práctico del curso
Imagina que trabajas en el equipo de desarrollo de RutaBus, una aplicación que ayuda a los ciudadanos a planificar sus trayectos en transporte público. La aplicación maneja información como:
- Paradas: puntos físicos de la ciudad ("Plaza Mayor", "Estación Norte", "Hospital Central"...), cada una con sus coordenadas.
- Líneas: recorridos de autobús (L1, L2, L3...) que conectan secuencias de paradas.
- Horarios: horas de paso de cada línea por cada parada.
- Red de transporte: el conjunto de paradas y conexiones, que más adelante modelaremos como un grafo.
Casi cualquier funcionalidad de RutaBus esconde un algoritmo:
| Funcionalidad de RutaBus | Problema algorítmico subyacente |
|---|---|
| "¿Cuál es mi parada más cercana?" | Búsqueda del mínimo en una colección |
| "Muéstrame los próximos autobuses ordenados por hora" | Ordenación |
| "¿Existe la parada 'Plaza Mayor'?" | Búsqueda |
| "¿Cómo llego de A a B en el menor tiempo?" | Camino mínimo en un grafo |
A lo largo del curso resolveremos estos problemas de forma progresiva: primero con soluciones sencillas y, a medida que aprendamos a analizarlas y diseñarlas mejor, con soluciones cada vez más eficientes.
¿Qué es un algoritmo (y qué no lo es)?
Un algoritmo es una secuencia finita y ordenada de pasos, definidos sin ambigüedad, que transforma unos datos de entrada en unos resultados de salida para resolver un problema concreto.
La analogía clásica es la receta de cocina: ingredientes (entrada), pasos precisos (proceso) y plato terminado (salida). Pero la analogía tiene límites: "añadir sal al gusto" es aceptable en una receta y no lo sería en un algoritmo, porque es ambiguo.
Conviene distinguir el algoritmo de conceptos cercanos con los que a menudo se confunde:
| Concepto | ¿Qué es? | ¿Es un algoritmo? |
|---|---|---|
| Algoritmo | Método abstracto de resolución, independiente del lenguaje | Sí |
| Programa | Implementación concreta de uno o más algoritmos en un lenguaje | No: es su materialización |
| Heurística informal | "Prueba cosas hasta que funcione" | No: no garantiza pasos definidos ni terminación |
| Especificación | Describe qué debe hacerse, no cómo | No: le falta el proceso |
| Proceso infinito | Un servidor que atiende peticiones sin parar | No como algoritmo clásico: no termina |
Ejemplos de cosas que no son algoritmos:
- "Busca la mejor ruta" → es un objetivo, no un método: no dice cómo.
- "Repite hasta que el resultado sea bueno" → ambiguo: ¿qué significa "bueno"?
- Un bucle
while Truesin condición de salida → no cumple la finitud.
La misma idea algorítmica puede implementarse en Python, Java o C y sigue siendo el mismo algoritmo. Esta independencia del lenguaje es la que nos permitirá, en el Módulo 2, analizar algoritmos sin preocuparnos de la máquina concreta donde se ejecutan.
Propiedades de un algoritmo
Para que una secuencia de pasos sea un algoritmo debe cumplir cinco propiedades clásicas (formuladas por Donald Knuth):
- Finitud: debe terminar tras un número finito de pasos. Un cálculo que nunca acaba no resuelve nada.
- Precisión (definición): cada paso debe estar definido sin ambigüedad. Dos personas (o dos ordenadores) que sigan el algoritmo con la misma entrada deben hacer exactamente lo mismo.
- Entradas definidas: debe estar claro qué datos recibe (pueden ser cero o más). En RutaBus: la lista de paradas y la posición del usuario.
- Salidas definidas: debe producir al menos un resultado, y debe estar claro cuál es. En RutaBus: la parada más cercana.
- Efectividad: cada paso debe ser lo bastante básico como para poder ejecutarse de forma mecánica en tiempo finito. "Calcular la distancia entre dos puntos" es efectivo; "intuir cuál queda más a mano" no lo es.
Veámoslo con un ejemplo de RutaBus. La instrucción "encuentra una parada que esté cerca del usuario" incumple la precisión (¿qué es "cerca"?) y la salida definida (¿cuál, si hay varias?). En cambio:
"Dadas las coordenadas del usuario y la lista completa de paradas, devuelve la parada cuya distancia euclídea al usuario sea mínima; si hay empate, la primera de la lista."
...cumple las cinco propiedades: termina (la lista es finita), es precisa (distancia euclídea, criterio de desempate), tiene entrada y salida claras, y cada paso es mecanizable.
Formas de expresar un algoritmo
Un mismo algoritmo puede expresarse con distintos niveles de formalidad. Los tres más habituales son el lenguaje natural, el pseudocódigo y el diagrama de flujo.
Lenguaje natural
Es la forma más accesible, ideal para comunicar la idea general:
Encontrar la parada más cercana:
- Toma la primera parada de la lista y considérala provisionalmente la más cercana.
- Recorre las demás paradas una a una.
- Para cada parada, calcula su distancia al usuario; si es menor que la de la candidata actual, esa parada pasa a ser la nueva candidata.
- Al acabar el recorrido, la candidata es la parada más cercana.
Su gran inconveniente: es fácil caer en ambigüedades y omitir casos límite (¿y si la lista está vacía?).
Pseudocódigo
El pseudocódigo es un punto intermedio: usa estructuras de programación (condicionales, bucles, variables) pero sin la sintaxis estricta de ningún lenguaje. Es la forma habitual de describir algoritmos en libros y documentación técnica.
ALGORITMO parada_mas_cercana
ENTRADA: usuario (coordenadas x, y), paradas (lista no vacía de paradas con nombre y coordenadas)
SALIDA: la parada con distancia mínima al usuario
mejor_parada ← paradas[0]
mejor_distancia ← distancia(usuario, paradas[0])
PARA CADA parada EN paradas[1..fin] HACER
d ← distancia(usuario, parada)
SI d < mejor_distancia ENTONCES
mejor_distancia ← d
mejor_parada ← parada
FIN SI
FIN PARA
DEVOLVER mejor_parada
FIN ALGORITMOObserva cómo el pseudocódigo obliga a concretar detalles que el lenguaje natural dejaba sueltos: la inicialización con la primera parada, la comparación estricta < (que resuelve los empates a favor de la primera encontrada) y el valor devuelto.
Diagrama de flujo
El diagrama de flujo representa gráficamente el control del algoritmo: rombos para decisiones, rectángulos para acciones. Es muy útil para visualizar bucles y bifurcaciones:
flowchart TD
A([Inicio]) --> B[mejor_parada = primera parada<br/>mejor_distancia = su distancia al usuario]
B --> C{¿Quedan paradas<br/>por revisar?}
C -- Sí --> D[Tomar siguiente parada<br/>d = distancia al usuario]
D --> E{¿d < mejor_distancia?}
E -- Sí --> F[Actualizar mejor_parada<br/>y mejor_distancia]
E -- No --> C
F --> C
C -- No --> G[/Devolver mejor_parada/]
G --> H([Fin])
Comparativa de las tres formas
| Forma | Precisión | Facilidad de lectura | Uso típico |
|---|---|---|---|
| Lenguaje natural | Baja | Muy alta | Comunicar la idea a cualquier persona |
| Pseudocódigo | Alta | Alta (para desarrolladores) | Diseñar y documentar antes de programar |
| Diagrama de flujo | Alta | Alta (visual) | Visualizar el flujo de control, formación |
En la práctica profesional suele empezarse por lenguaje natural (entender el problema), refinarse a pseudocódigo (diseñar la solución) y finalmente traducirse a código.
Del pseudocódigo al código Python: primer ejemplo en RutaBus
Traduzcamos el pseudocódigo anterior a Python. Representaremos cada parada como un diccionario con su nombre y sus coordenadas (en un plano simplificado de la ciudad):
import math
# Datos ficticios de RutaBus: paradas con coordenadas (x, y) en km
paradas = [
{"nombre": "Plaza Mayor", "x": 0.0, "y": 0.0},
{"nombre": "Estación Norte", "x": 2.5, "y": 4.0},
{"nombre": "Hospital Central", "x": 1.0, "y": 1.5},
{"nombre": "Parque del Río", "x": 3.0, "y": 0.5},
]
def distancia(x1, y1, x2, y2):
"""Distancia euclídea entre dos puntos del plano."""
return math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
def parada_mas_cercana(usuario_x, usuario_y, paradas):
"""Devuelve la parada más cercana a la posición del usuario.
Entrada: coordenadas del usuario y lista NO vacía de paradas.
Salida: el diccionario de la parada con distancia mínima.
"""
# Paso 1: la primera parada es la candidata inicial
mejor_parada = paradas[0]
mejor_distancia = distancia(usuario_x, usuario_y,
mejor_parada["x"], mejor_parada["y"])
# Paso 2: recorrer el resto de paradas
for parada in paradas[1:]:
d = distancia(usuario_x, usuario_y, parada["x"], parada["y"])
# Paso 3: si mejora a la candidata, la sustituye
if d < mejor_distancia:
mejor_distancia = d
mejor_parada = parada
# Paso 4: al terminar el bucle, la candidata es la más cercana
return mejor_parada
# El usuario está en (0.8, 1.0)
resultado = parada_mas_cercana(0.8, 1.0, paradas)
print(f"Parada más cercana: {resultado['nombre']}")
# Salida: Parada más cercana: Hospital CentralAnalicemos el código pieza a pieza:
distancia: función auxiliar que aplica el teorema de Pitágoras. Encapsularla en una función hace el algoritmo principal más legible y refuerza la propiedad de efectividad: cada paso es una operación básica y mecanizable.- Inicialización con
paradas[0]: igual que en el pseudocódigo, la primera parada es la candidata inicial. Esto evita inventar valores artificiales como "infinito" y garantiza que siempre devolvemos una parada real (si la lista no está vacía). for parada in paradas[1:]: recorre desde la segunda parada hasta el final. Es la traducción directa delPARA CADAdel pseudocódigo.if d < mejor_distancia: la comparación estricta hace que, en caso de empate, se conserve la parada encontrada primero — exactamente el criterio de desempate que definimos en la especificación.return mejor_parada: la salida definida del algoritmo.
Fíjate en que el algoritmo es el mismo en las tres representaciones (natural, pseudocódigo, Python): lo que cambia es el nivel de detalle y la sintaxis, no la idea.
Motivación: hay algoritmos mejores que otros
Nuestro algoritmo funciona, pero ¿es bueno? Para encontrar la parada más cercana revisa todas las paradas, una por una. Con las 4 paradas del ejemplo es instantáneo. Pero imagina la red real de una gran ciudad:
- Con 4 paradas → 4 distancias calculadas.
- Con 5.000 paradas → 5.000 distancias calculadas.
- Si además la app calcula esto para miles de usuarios por segundo... el coste se multiplica.
Y hay más de una forma de resolver el mismo problema. Por adelantar la intuición: buscar una parada por su nombre recorriendo la lista entera funciona, pero si la lista está ordenada existe una técnica (la búsqueda binaria, que veremos en el Módulo 4) que con 5.000 paradas necesita apenas una docena de comparaciones en lugar de 5.000. Mismo problema, mismo resultado, esfuerzo radicalmente distinto.
Esta es la gran idea del curso: para un mismo problema suelen existir varios algoritmos correctos, y las diferencias de eficiencia entre ellos pueden ser enormes. Aprender a comparar algoritmos con rigor, sin cronómetros ni máquinas concretas, es justamente el objetivo de la lección de notación asintótica (01-03) y de todo el Módulo 2.
Errores Comunes y Consejos
- Confundir el algoritmo con el programa. Si tu solución solo existe "en Python", te costará razonar sobre ella y compararla con alternativas. Consejo: acostúmbrate a esbozar primero el pseudocódigo; el código saldrá casi solo.
- Dejar pasos ambiguos. "Elegir la mejor opción" no es un paso de algoritmo hasta que definas mejor con un criterio medible (distancia mínima, hora más temprana...). Revisa cada paso preguntándote: ¿podría ejecutarlo una máquina sin pedir aclaraciones?
- Olvidar los casos límite en las entradas. Nuestro
parada_mas_cercanafalla con una lista vacía (paradas[0]lanzaIndexError). Un algoritmo bien especificado dice qué entradas admite; un programa robusto además las valida. - No definir el criterio de desempate. Si dos paradas están a la misma distancia, ¿cuál se devuelve? Nuestro
<estricto lo resuelve (gana la primera), pero hay que decidirlo conscientemente, no por accidente. - Escribir código antes de entender el problema. Consejo práctico: escribe primero la entrada y la salida esperadas con un ejemplo concreto (como hicimos con el usuario en (0.8, 1.0)). Si no puedes dar un ejemplo, aún no entiendes el problema.
Ejercicios
Ejercicio 1
Indica cuáles de las siguientes descripciones cumplen las cinco propiedades de un algoritmo y, si no, qué propiedad incumplen:
a) "Recorre las paradas de la línea L1 y devuelve cuántas hay." b) "Prueba rutas al azar hasta dar con una que te guste." c) "Mientras el autobús no llegue, sigue esperando." d) "Dada una lista de horarios, devuelve el primero posterior a la hora actual; si no hay ninguno, devuelve None."
Ejercicio 2
Escribe en pseudocódigo (no en Python) un algoritmo hay_parada(nombre, paradas) que devuelva VERDADERO si existe una parada con ese nombre en la lista y FALSO en caso contrario. Asegúrate de que cumple las cinco propiedades.
Ejercicio 3
Modifica la función parada_mas_cercana en Python para que devuelva las dos paradas más cercanas al usuario (la más cercana y la segunda más cercana), sin ordenar la lista completa. Supón que la lista tiene al menos dos paradas.
Soluciones
Solución 1:
a) Sí es un algoritmo: entrada (lista de paradas de L1), salida (un número), pasos precisos, finito y efectivo. b) No: incumple la precisión ("que te guste" es ambiguo) y potencialmente la finitud (podría no acabar nunca). c) No: incumple la finitud (puede no terminar) y no tiene salida definida. d) Sí es un algoritmo: nota cómo la cláusula "si no hay ninguno, devuelve None" es la que garantiza la salida definida para todas las entradas. Sin ella, sería una especificación incompleta.
Solución 2:
ALGORITMO hay_parada
ENTRADA: nombre (texto), paradas (lista de paradas, posiblemente vacía)
SALIDA: VERDADERO si alguna parada se llama `nombre`, FALSO si no
PARA CADA parada EN paradas HACER
SI parada.nombre = nombre ENTONCES
DEVOLVER VERDADERO
FIN SI
FIN PARA
DEVOLVER FALSO
FIN ALGORITMOComprobación de propiedades: termina (la lista es finita), cada paso es preciso y efectivo (comparar textos), la entrada y la salida están definidas para cualquier lista, incluida la vacía (devuelve FALSO). Error común: olvidar el DEVOLVER FALSO final, que deja la salida sin definir cuando la parada no existe.
Solución 3:
def dos_paradas_mas_cercanas(usuario_x, usuario_y, paradas):
"""Devuelve (mas_cercana, segunda_mas_cercana). Requiere len(paradas) >= 2."""
d0 = distancia(usuario_x, usuario_y, paradas[0]["x"], paradas[0]["y"])
d1 = distancia(usuario_x, usuario_y, paradas[1]["x"], paradas[1]["y"])
# Ordenamos manualmente las dos primeras candidatas
if d0 <= d1:
primera, d_primera, segunda, d_segunda = paradas[0], d0, paradas[1], d1
else:
primera, d_primera, segunda, d_segunda = paradas[1], d1, paradas[0], d0
for parada in paradas[2:]:
d = distancia(usuario_x, usuario_y, parada["x"], parada["y"])
if d < d_primera:
# La nueva parada desplaza a la primera; la antigua primera pasa a segunda
segunda, d_segunda = primera, d_primera
primera, d_primera = parada, d
elif d < d_segunda:
# Solo mejora a la segunda
segunda, d_segunda = parada, d
return primera, segundaLa clave está en mantener dos candidatas y, cuando aparece una parada mejor que la primera, no perder la antigua primera: pasa a ocupar el puesto de segunda. Un error muy común es sobrescribir primera sin antes copiarla en segunda.
Conclusión
En esta lección hemos definido qué es un algoritmo —una secuencia finita, precisa y efectiva de pasos con entradas y salidas bien definidas— y qué lo distingue de un programa, una heurística vaga o una simple especificación. Hemos visto tres formas de expresarlo (lenguaje natural, pseudocódigo y diagrama de flujo) y hemos recorrido el camino completo hasta el código Python con nuestro primer problema real de RutaBus: encontrar la parada más cercana al usuario. También hemos plantado una semilla importante: un mismo problema admite varios algoritmos correctos, y unos son mucho más eficientes que otros. En la próxima lección, Tipos de Algoritmos, pondremos orden en ese universo: clasificaremos los algoritmos por su enfoque (iterativos y recursivos), por su propósito (búsqueda, ordenación, grafos...) y por otras dimensiones que nos ayudarán a elegir la herramienta adecuada para cada problema de RutaBus.
Curso de Análisis y Diseño de Algoritmos
Módulo 1: Introducción a los Algoritmos
Módulo 2: Análisis de Algoritmos
- Análisis de Complejidad Temporal
- Análisis de Complejidad Espacial
- Casos de Complejidad: Mejor, Peor y Promedio
Módulo 3: Estrategias de Diseño de Algoritmos
Módulo 4: Algoritmos Clásicos
- Búsqueda Binaria
- Ordenamiento por Inserción
- Ordenamiento por Mezcla (Merge Sort)
- Ordenamiento Rápido (Quick Sort)
- Algoritmo de Dijkstra
- Algoritmo de Floyd-Warshall
