Introducción
Los algoritmos greedy (o voraces) son una clase de algoritmos que construyen una solución paso a paso, eligiendo en cada paso la opción que parece ser la mejor en ese momento. Estos algoritmos son simples y eficientes, pero no siempre garantizan la solución óptima para todos los problemas.
Características de los Algoritmos Greedy
- Selección Greedy: En cada paso, se elige la opción que parece ser la mejor en ese momento.
- Solución Parcial: La solución se construye paso a paso, añadiendo elementos a una solución parcial.
- Irrevocabilidad: Una vez que se toma una decisión, no se puede deshacer.
- Optimalidad Local: Cada paso se toma con la esperanza de que la solución localmente óptima conduzca a una solución globalmente óptima.
Ejemplos de Problemas Resueltos con Algoritmos Greedy
- Problema del Cambio de Monedas: Dado un conjunto de denominaciones de monedas y una cantidad total, encontrar la mínima cantidad de monedas que sumen esa cantidad.
- Problema del Viajante de Comercio (versión aproximada): Encontrar una ruta que visite cada ciudad exactamente una vez y minimice la distancia total recorrida.
- Problema de la Mochila Fraccionaria: Dado un conjunto de objetos con pesos y valores, y una capacidad máxima de la mochila, maximizar el valor total de los objetos que se pueden llevar.
Ejemplo Práctico: Problema del Cambio de Monedas
Descripción del Problema
Dado un conjunto de denominaciones de monedas y una cantidad total, encontrar la mínima cantidad de monedas que sumen esa cantidad.
Algoritmo Greedy para el Problema del Cambio de Monedas
- Ordenar las denominaciones de monedas en orden descendente.
- Inicializar la cantidad total restante como la cantidad dada.
- Para cada denominación de moneda, tomar tantas monedas como sea posible sin exceder la cantidad total restante.
- Restar el valor de las monedas tomadas de la cantidad total restante.
- Repetir hasta que la cantidad total restante sea cero.
Implementación en Python
def cambio_monedas(cantidad, denominaciones): # Ordenar las denominaciones en orden descendente denominaciones.sort(reverse=True) # Inicializar el resultado resultado = [] # Iterar sobre cada denominación for moneda in denominaciones: # Tomar tantas monedas como sea posible while cantidad >= moneda: cantidad -= moneda resultado.append(moneda) # Verificar si se pudo dar el cambio exacto if cantidad != 0: return "No se puede dar el cambio exacto con las denominaciones dadas." return resultado # Ejemplo de uso cantidad = 87 denominaciones = [25, 10, 5, 1] print(cambio_monedas(cantidad, denominaciones))
Explicación del Código
- Ordenar las denominaciones:
denominaciones.sort(reverse=True)
ordena las denominaciones en orden descendente. - Inicializar el resultado:
resultado = []
inicializa una lista vacía para almacenar las monedas usadas. - Iterar sobre cada denominación:
for moneda in denominaciones:
itera sobre cada denominación de moneda. - Tomar tantas monedas como sea posible:
while cantidad >= moneda:
toma tantas monedas de la denominación actual como sea posible sin exceder la cantidad total restante. - Restar el valor de las monedas tomadas:
cantidad -= moneda
resta el valor de las monedas tomadas de la cantidad total restante. - Verificar si se pudo dar el cambio exacto:
if cantidad != 0:
verifica si la cantidad total restante es cero. Si no es cero, significa que no se pudo dar el cambio exacto con las denominaciones dadas.
Ejercicio Práctico
Problema del Intervalo Máximo
Dado un conjunto de intervalos, encontrar el subconjunto máximo de intervalos no superpuestos.
Descripción del Problema
Dado un conjunto de intervalos [start, end]
, encontrar el subconjunto máximo de intervalos que no se superpongan.
Algoritmo Greedy para el Problema del Intervalo Máximo
- Ordenar los intervalos por su tiempo de finalización.
- Inicializar el tiempo de finalización del último intervalo seleccionado como negativo infinito.
- Iterar sobre cada intervalo.
- Si el tiempo de inicio del intervalo actual es mayor o igual al tiempo de finalización del último intervalo seleccionado, seleccionar el intervalo actual y actualizar el tiempo de finalización del último intervalo seleccionado.
Implementación en Python
def intervalo_maximo(intervalos): # Ordenar los intervalos por su tiempo de finalización intervalos.sort(key=lambda x: x[1]) # Inicializar el tiempo de finalización del último intervalo seleccionado fin_ultimo_intervalo = float('-inf') # Inicializar el resultado resultado = [] # Iterar sobre cada intervalo for intervalo in intervalos: # Si el tiempo de inicio del intervalo actual es mayor o igual al tiempo de finalización del último intervalo seleccionado if intervalo[0] >= fin_ultimo_intervalo: # Seleccionar el intervalo actual resultado.append(intervalo) # Actualizar el tiempo de finalización del último intervalo seleccionado fin_ultimo_intervalo = intervalo[1] return resultado # Ejemplo de uso intervalos = [(1, 3), (2, 4), (3, 5), (0, 6), (5, 7), (8, 9)] print(intervalo_maximo(intervalos))
Explicación del Código
- Ordenar los intervalos:
intervalos.sort(key=lambda x: x[1])
ordena los intervalos por su tiempo de finalización. - Inicializar el tiempo de finalización del último intervalo seleccionado:
fin_ultimo_intervalo = float('-inf')
inicializa el tiempo de finalización del último intervalo seleccionado como negativo infinito. - Inicializar el resultado:
resultado = []
inicializa una lista vacía para almacenar los intervalos seleccionados. - Iterar sobre cada intervalo:
for intervalo in intervalos:
itera sobre cada intervalo. - Seleccionar el intervalo actual:
if intervalo[0] >= fin_ultimo_intervalo:
selecciona el intervalo actual si su tiempo de inicio es mayor o igual al tiempo de finalización del último intervalo seleccionado. - Actualizar el tiempo de finalización del último intervalo seleccionado:
fin_ultimo_intervalo = intervalo[1]
actualiza el tiempo de finalización del último intervalo seleccionado.
Resumen
En esta sección, hemos aprendido sobre los algoritmos greedy, sus características y cómo se aplican a problemas específicos. Hemos visto ejemplos prácticos como el problema del cambio de monedas y el problema del intervalo máximo, y hemos implementado soluciones en Python. Los algoritmos greedy son una herramienta poderosa para resolver ciertos tipos de problemas de manera eficiente, aunque no siempre garantizan la solución óptima.
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