Saltar al contenido

Estrategias de diseño

Las seis familias con las que se ataca un problema: divide y vencerás, voraces, programación dinámica, vuelta atrás, ramificación y poda, y algoritmos probabilísticos.

Divide y vencerás

La estrategia consiste en partir el problema en subproblemas del mismo tipo pero más pequeños, resolverlos por separado (normalmente de forma recursiva) y combinar sus soluciones en la del problema original. Se detiene al llegar a un caso tan pequeño que se resuelve directamente, llamado caso base o caso trivial.

Es un enfoque descendente o top-down: se parte del problema completo y se va bajando. Mergesort, quicksort y la búsqueda binaria son sus ejemplos canónicos, y de ahí sale su rasgo típico: partir el problema por la mitad repetidamente introduce un factor logarítmico en la complejidad.

Divide y vencerás exige que los subproblemas sean independientes entre sí. Cuando los subproblemas se solapan y se repiten, la estrategia correcta ya no es esta sino la programación dinámica.

Para el examen

  • En qué consiste: partir en subproblemas independientes, resolver y combinar

  • Ejemplos clásicos: mergesort, quicksort y búsqueda binaria

Algoritmos voraces

Un algoritmo voraz (greedy) construye la solución paso a paso tomando en cada momento la opción que parece mejor localmente, sin mirar hacia adelante y sin volver atrás a revisar lo decidido. Es la estrategia más rápida y la más simple de programar.

pseudocodigo
funcion voraz(candidatos):
    solucion = vacio
    mientras la solucion no este completa y queden candidatos:
        c = mejor(candidatos)          // la decisión se toma aquí
        quitar c de candidatos
        si esFactible(solucion + c):   // y no se revisa nunca más
            solucion = solucion + c
    devolver solucion

Su límite es que no siempre llega al óptimo global: una buena decisión local puede cerrar el camino a una solución mejor. Solo se garantiza el óptimo en problemas con la estructura adecuada, y ahí están sus casos de éxito clásicos: Dijkstra, Prim y Kruskal son voraces y sí alcanzan el óptimo.

Para el examen

  • Cómo decide: la mejor opción local en cada paso, sin volver atrás

  • Lo que no garantiza: el óptimo global

  • Ejemplos: Dijkstra, Prim y Kruskal

Programación dinámica y memoización

La programación dinámica ataca problemas cuyos subproblemas se repiten una y otra vez. La idea es no calcular dos veces lo mismo: se guarda el resultado de cada subproblema y se reutiliza cuando vuelve a hacer falta. Con eso, cálculos que por fuerza bruta serían exponenciales bajan a tiempo polinómico.

Tiene dos formas de organizarse. La ascendente o bottom-up empieza por los subproblemas más pequeños y va rellenando una tabla hasta llegar al grande. La descendente o top-down parte del problema completo, baja recursivamente y guarda por el camino lo que va calculando: esa caché de resultados parciales es la memoización.

pseudocodigo
// Sin memoización: cada llamada vuelve a calcular lo mismo -> exponencial.
funcion fib(n):
    si n <= 1: devolver n
    devolver fib(n - 1) + fib(n - 2)

// Con memoización: cada subproblema se calcula una sola vez -> O(n).
memo = { }
funcion fibMemo(n):
    si n <= 1: devolver n
    si n esta en memo: devolver memo[n]
    memo[n] = fibMemo(n - 1) + fibMemo(n - 2)
    devolver memo[n]

La programación dinámica cambia tiempo por memoria: gasta espacio en guardar resultados parciales para no repetir cálculos. Divide y vencerás no los guarda porque sus subproblemas no se repiten.

Para el examen

  • Cuándo se aplica: cuando los subproblemas se SOLAPAN

  • Memoización: la caché del enfoque descendente (top-down)

  • Enfoque ascendente: rellena la tabla desde los casos base

Vuelta atrás (backtracking)

La vuelta atrás explora sistemáticamente el árbol de todas las soluciones posibles. Va construyendo una solución parcial y, en cuanto detecta que por ese camino no puede salir nada válido, deshace la última decisión y prueba la siguiente alternativa. De ahí el nombre.

Es una búsqueda exhaustiva, así que encuentra la solución si existe, pero su coste crece muy deprisa: en el peor caso recorre el árbol entero, con complejidad exponencial. Los casos típicos son los sudokus, los laberintos, el problema de las ocho reinas y la coloración de mapas.

Para el examen

  • Cómo funciona: explora el árbol de soluciones y deshace la última decisión al llegar a un callejón sin salida

  • Coste: exhaustivo y exponencial

  • Ejemplos: ocho reinas, sudoku y laberintos

Ramificación y poda

Ramificación y poda es la vuelta atrás afinada. Ramificar es abrir las alternativas de cada decisión, igual que antes. Lo nuevo es la poda: antes de bajar por una rama se estima su mejor resultado posible, y si esa estimación ya es peor que la mejor solución encontrada hasta el momento, la rama se descarta entera sin explorarla.

El resultado es el mismo óptimo que daría la búsqueda exhaustiva, pero visitando muchos menos nodos. En el peor caso sigue siendo exponencial, porque la poda no garantiza recortar nada: si ninguna estimación permite descartar, se explora todo.

La vuelta atrás retrocede cuando comprueba que el camino no es válido; ramificación y poda descarta ramas antes de recorrerlas, usando cotas y resultados ya obtenidos.

Para el examen

  • Qué añade al backtracking: cotas que descartan ramas enteras

  • Cuándo poda: cuando la mejor estimación de la rama ya es peor que la solución actual

  • Qué conserva: el mismo óptimo, con menos nodos; el peor caso sigue siendo exponencial

Algoritmos probabilísticos

Un algoritmo probabilístico o aleatorizado toma alguna decisión al azar durante su ejecución, de modo que dos ejecuciones sobre los mismos datos no tienen por qué comportarse igual. Se recurre a ellos cuando la solución exacta es demasiado cara y basta con una respuesta buena en un tiempo razonable.

FamiliaQué garantizaQué no garantiza
MontecarloTerminar dentro de un tiempo acotadoQue el resultado sea correcto: hay una probabilidad de error, que se puede reducir repitiendo
Las VegasQue el resultado sea correcto siempreCuánto va a tardar: el tiempo de ejecución es variable

Montecarlo arriesga el resultado para controlar el tiempo; Las Vegas arriesga el tiempo para asegurar el resultado.

Para el examen

  • Montecarlo: tiempo acotado, resultado con probabilidad de error

  • Las Vegas: resultado siempre correcto, tiempo variable