Emtix

Gestión de la Intratabilidad: Más Allá de NP-Completitud

Publicado el 1 de diciembre de 2025

Gestión de la Intratabilidad: Estrategias Post-NP-Completo

Una vez que probamos que un problema es NP-completo, el trabajo apenas comienza. Los problemas reales no desaparecen por ser difíciles. Este capítulo explora estrategias prácticas para obtener soluciones útiles.


1. Definición: El Desafío de la Intratabilidad

Problemas NP-Completos en la Práctica

Realidad: Demostrar que un problema es NP-completo NO es el final.

Situación:

  • No existe algoritmo polinomial exacto (probablemente)
  • Pero el problema real sigue existiendo
  • Se necesita una solución práctica

Pregunta clave: ¿Qué hacemos cuando la solución óptima es inalcanzable?

Limitación de la Teoría

Lo que NP-Completitud Establece

La teoría de NP-Completitud establece la dificultad de obtener:

  • La respuesta exacta
  • En tiempo polinomial
  • Para el peor caso

Pero NO dice nada sobre:

  • Aproximaciones cercanas
  • Casos promedio
  • Instancias específicas
  • Soluciones subóptimas aceptables

2. Propósito: Soluciones Prácticas y Útiles

Objetivo: Soluciones Computables

Objetivo: Encontrar métodos que ofrezcan soluciones útiles y computables en la práctica.

Requisitos:

  1. Tiempo de ejecución razonable
  2. Calidad de solución aceptable
  3. Idealmente, con garantías demostrables
  4. Aplicable a instancias reales

Tipos de Garantías

Tipo de SoluciónGarantíaTiempoOptimalidad
ExactaSolución óptimaExponencial100%
AproximaciónFactor constante del óptimoPolinomial≥ X%
HeurísticaSin garantía formalRápidoVariable
Caso promedioBuena en promedioVariableDepende

3. Tres Estrategias Principales

Vías para Abordar Problemas Difíciles

1. Algoritmos Rápidos en Caso Promedio

  • Backtracking con poda sustancial
  • Funciona bien en instancias típicas
  • Sin garantías de peor caso

2. Heurísticas

  • Métodos rápidos sin garantías
  • Simulated Annealing, algoritmos genéticos
  • Solución “suficientemente buena” en práctica

3. Algoritmos de Aproximación

  • Garantía demostrable de calidad
  • Factor de aproximación acotado
  • Balance entre velocidad y garantías
Comparación de estrategias
═══════════════════════════════════════════════════════════
ESTRATEGIAS PARA PROBLEMAS NP-COMPLETOS
═══════════════════════════════════════════════════════════

1. CASO PROMEDIO (Backtracking optimizado):
  Complejidad: Exponencial teórica
  Práctica: Rápido en instancias típicas
  Garantía: Solución ÓPTIMA (si termina)
  Problema: Peor caso sigue siendo exponencial

  Ejemplo: Sudoku con poda inteligente
  - Peor caso: horas
  - Caso típico: < 1 segundo

2. HEURÍSTICAS (Sin garantías):
  Complejidad: Polinomial o mejor
  Práctica: Muy rápido
  Garantía: NINGUNA formal
  Ventaja: Funciona bien en práctica

  Ejemplo: Hill Climbing para TSP
  - Tiempo: O(n²)
  - Calidad: 10-30% sobre óptimo típico
  - Riesgo: Óptimos locales

3. APROXIMACIÓN (Con garantías):
  Complejidad: Polinomial
  Práctica: Eficiente
  Garantía: Factor α del óptimo
  Trade-off: Nunca óptima, pero cerca

  Ejemplo: 2-aproximación para Vertex Cover
  - Tiempo: O(|E|)
  - Calidad: ≤ 2 × óptimo SIEMPRE
  - Predecible: Peor caso acotado

4. Algoritmos de Aproximación

Aproximación con Garantías

Algoritmo de Aproximación: Método polinomial que garantiza solución dentro de un factor de aproximación del óptimo.

Factor de aproximación α:

  • Para minimización: (solución_aprox / óptimo) α\leq \alpha
  • Para maximización: (óptimo / solución_aprox) α\leq \alpha

Ventaja clave: Garantía válida para TODAS las instancias, no solo casos típicos.

Ejemplo Clásico: Vertex Cover

Vertex Cover (Cubierta de Vértices)

Problema: Encontrar el subconjunto más pequeño de vértices que toca cada arista.

NP-Completo: No hay algoritmo polinomial exacto (probablemente)

Solución: Algoritmo de 2-aproximación

Algoritmo de 2-aproximación para Vertex Cover
def vertex_cover_approx(graph):
    """
    Algoritmo de 2-aproximación para Vertex Cover

    Garantía: |solución| ≤ 2 × |óptimo|
    Tiempo: O(|E|) - lineal en aristas

    Args:
        graph: grafo con aristas

    Returns:
        cover: conjunto de vértices (cubierta)
    """
    cover = set()
    edges = set(graph.edges)  # Copiar aristas

    print("Algoritmo de 2-aproximación para Vertex Cover\n")
    print("Paso a paso:\n")

    step = 1
    while edges:
        # Seleccionar arista arbitraria
        u, v = edges.pop()

        print(f"Paso {step}: Seleccionar arista ({u}, {v})")
        print(f"  Agregar vértices {u} y {v} a la cubierta")

        # Agregar ambos extremos a la cubierta
        cover.add(u)
        cover.add(v)

        # Eliminar todas las aristas incidentes a u o v
        edges_to_remove = {e for e in edges if u in e or v in e}
        edges -= edges_to_remove

        print(f"  Eliminar {len(edges_to_remove)} aristas cubiertas")
        print(f"  Aristas restantes: {len(edges)}")
        print()

        step += 1

    return cover

# Ejemplo de uso
print("="*60)
print("VERTEX COVER: ALGORITMO DE 2-APROXIMACIÓN")
print("="*60 + "\n")

class Graph:
    def __init__(self):
        self.vertices = set()
        self.edges = []

    def add_edge(self, u, v):
        self.vertices.add(u)
        self.vertices.add(v)
        self.edges.append((u, v))

    def __repr__(self):
        return f"Vértices: {sorted(self.vertices)}\nAristas: {self.edges}"

# Crear grafo de ejemplo
G = Graph()
edges_input = [(1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (1, 6), (2, 4)]

for u, v in edges_input:
    G.add_edge(u, v)

print("Grafo:")
print(G)
print()

print("Visualización:")
print("""
    1 --- 2
    |     |\\
    |     | 3
    |     |/
    6 --- 5 --- 4
""")

print("="*60)

# Ejecutar algoritmo
cover = vertex_cover_approx(G)

print("="*60)
print("RESULTADO:")
print("="*60)
print(f"Cubierta encontrada: {sorted(cover)}")
print(f"Tamaño: {len(cover)}")

# Verificar que es cubierta válida
print("\nVerificación:")
for u, v in edges_input:
    if u in cover or v in cover:
        print(f"  Arista ({u},{v}): ✓ cubierta por {u if u in cover else v}")
    else:
        print(f"  Arista ({u},{v}): ✗ NO cubierta")

print("\n" + "="*60)
print("ANÁLISIS DE GARANTÍA:")
print("="*60)
print("""
¿Por qué es 2-aproximación?

Observación clave: Cada arista seleccionada requiere AL MENOS
uno de sus extremos en el óptimo.

Si seleccionamos k aristas:
  - Agregamos 2k vértices a nuestra solución
  - El óptimo necesita al menos k vértices
  - Ratio: 2k / k = 2

Por tanto: |solución| ≤ 2 × |óptimo|

En este ejemplo:
  - Nuestra solución: 6 vértices
  - Óptimo probable: 3-4 vértices
  - Ratio: 6/3 = 2 o 6/4 = 1.5
  - Dentro de la garantía ✓
""")

Teorema: El algoritmo de aproximación para Vertex Cover es una 2-aproximación.

Demostración:

Sea:

  • CC = cubierta producida por el algoritmo
  • CC^* = cubierta óptima (mínima)
  • AA = conjunto de aristas seleccionadas durante el algoritmo

Observación 1: C=2A|C| = 2|A|

  • Por cada arista en AA, agregamos exactamente 2 vértices

Observación 2: Las aristas en AA no comparten vértices

  • Cuando seleccionamos (u,v)(u,v), eliminamos todas las aristas incidentes a uu y vv
  • Ninguna arista futura puede compartir extremos con (u,v)(u,v)

Observación 3: CA|C^*| \geq |A|

  • Cada arista en AA debe ser cubierta por CC^*
  • Como las aristas en AA no comparten vértices
  • Se necesita AL MENOS un vértice distinto por arista
  • Por tanto: CA|C^*| \geq |A|

Conclusión:

C=2A2C|C| = 2|A| \leq 2|C^*|

Por tanto, el algoritmo es una 2-aproximación. □

Quiz

¿Qué garantiza un algoritmo de α-aproximación?


5. Heurísticas: Simulated Annealing

Simulated Annealing (Recocido Simulado)

Heurística de búsqueda inspirada en el proceso metalúrgico de recocido.

Idea central: Permitir movimientos “cuesta arriba” ocasionales para escapar de óptimos locales.

Analogía: Enfriar metal lentamente para alcanzar estado de mínima energía.

Conceptos Fundamentales

Componentes de Simulated Annealing

Requisitos:

  1. Representación concisa del estado (solución candidata)
  2. Función de costo para evaluar calidad
  3. Función de vecindad (transiciones posibles)
  4. Programa de enfriamiento (schedule)

Componentes Clave:

1. Temperatura (T):

  • Controla probabilidad de aceptar empeoramientos
  • Alta T → acepta casi todo (exploración)
  • Baja T → solo mejoras (explotación)

2. Criterio de Aceptación:

  • Mejora (Δcost < 0): SIEMPRE aceptar
  • Empeoramiento (Δcost > 0): aceptar con probabilidad exponencial decreciente según temperatura

3. Programa de Enfriamiento:

  • Temperatura inicial: T1T_1 (típicamente 1.0)
  • Decremento: Ti=α×Ti1T_i = \alpha \times T_{i-1} (α0.95\alpha \approx 0.95)
  • Criterio de parada: T<TminT < T_{min} o iteraciones

Pseudocódigo:

estado = solución_inicial()
T = T_inicial

mientras T > T_min:
    para i = 1 hasta iter_por_temp:
        nuevo = vecino(estado)
        Δ = costo(nuevo) - costo(estado)
        
        si Δ < 0:                    # Mejora
            estado = nuevo
        sino:                        # Empeoramiento
            si random() < e^(-Δ/T):
                estado = nuevo       # Aceptar ocasionalmente
    
    T = α × T                        # Enfriar

retornar estado

Implementación Completa para TSP

Simulated Annealing para TSP
import random
import math

def tsp_cost(tour, distances):
    """Calcular costo total del tour"""
    cost = 0
    n = len(tour)
    for i in range(n):
        cost += distances[tour[i]][tour[(i + 1) % n]]
    return cost

def two_opt_swap(tour, i, j):
    """Generar vecino intercambiando segmento [i:j]"""
    new_tour = tour[:i] + tour[i:j+1][::-1] + tour[j+1:]
    return new_tour

def simulated_annealing_tsp(distances, T_initial=1.0, T_min=0.001,
                            alpha=0.95, iter_per_temp=100):
    """
    Simulated Annealing para TSP

    Args:
        distances: matriz de distancias
        T_initial: temperatura inicial
        T_min: temperatura mínima
        alpha: factor de enfriamiento
        iter_per_temp: iteraciones por temperatura

    Returns:
        mejor_tour, mejor_costo, historia
    """
    n = len(distances)

    # Solución inicial: orden aleatorio
    current_tour = list(range(n))
    random.shuffle(current_tour)
    current_cost = tsp_cost(current_tour, distances)

    # Mejor solución encontrada
    best_tour = current_tour[:]
    best_cost = current_cost

    # Historia para visualización
    history = [(0, current_cost, best_cost)]

    T = T_initial
    iteration = 0

    print(f"Iniciando SA para TSP con {n} ciudades")
    print(f"Costo inicial: {current_cost:.2f}\n")

    while T > T_min:
        improvements_at_T = 0

        for _ in range(iter_per_temp):
            iteration += 1

            # Generar vecino (2-opt swap)
            i = random.randint(0, n - 2)
            j = random.randint(i + 1, n - 1)

            new_tour = two_opt_swap(current_tour, i, j)
            new_cost = tsp_cost(new_tour, distances)

            delta = new_cost - current_cost

            # Decidir aceptación
            if delta < 0:
                # Mejora: siempre aceptar
                current_tour = new_tour
                current_cost = new_cost
                improvements_at_T += 1

                # Actualizar mejor
                if current_cost < best_cost:
                    best_tour = current_tour[:]
                    best_cost = current_cost
            else:
                # Empeoramiento: aceptar con probabilidad
                acceptance_prob = math.exp(-delta / T)
                if random.random() < acceptance_prob:
                    current_tour = new_tour
                    current_cost = new_cost

            history.append((iteration, current_cost, best_cost))

        # Reportar progreso
        print(f"T={T:.4f} | Costo actual: {current_cost:.2f} | "
              f"Mejor: {best_cost:.2f} | Mejoras: {improvements_at_T}")

        # Enfriar
        T *= alpha

    print(f"\n{'='*60}")
    print(f"Finalizado: Mejor costo = {best_cost:.2f}")

    return best_tour, best_cost, history

# Ejemplo de uso
print("="*60)
print("SIMULATED ANNEALING PARA TSP")
print("="*60 + "\n")

# Crear instancia TSP pequeña
n = 8
distances = [[0] * n for _ in range(n)]

# Generar distancias aleatorias simétricas
random.seed(42)
for i in range(n):
    for j in range(i + 1, n):
        dist = random.randint(10, 100)
        distances[i][j] = dist
        distances[j][i] = dist

print(f"Instancia TSP con {n} ciudades")
print("\nMatriz de distancias:")
print(f"{'':>4}", end="")
for i in range(n):
    print(f"{i:>4}", end="")
print()

for i in range(n):
    print(f"{i:>3}", end=" ")
    for j in range(n):
        if i == j:
            print(f"{'·':>4}", end="")
        else:
            print(f"{distances[i][j]:>4}", end="")
    print()

print("\n" + "="*60)
print("EJECUTANDO SIMULATED ANNEALING:")
print("="*60 + "\n")

# Ejecutar SA
best_tour, best_cost, history = simulated_annealing_tsp(
    distances,
    T_initial=10.0,
    T_min=0.01,
    alpha=0.95,
    iter_per_temp=50
)

print("\n" + "="*60)
print("RESULTADO FINAL:")
print("="*60)
print(f"Mejor tour: {best_tour}")
print(f"Costo: {best_cost:.2f}")

# Comparar con greedy
print("\n" + "="*60)
print("COMPARACIÓN CON GREEDY:")
print("="*60)

def greedy_tsp(distances):
    """TSP greedy: siempre ir a ciudad más cercana no visitada"""
    n = len(distances)
    tour = [0]
    unvisited = set(range(1, n))

    while unvisited:
        current = tour[-1]
        nearest = min(unvisited, key=lambda city: distances[current][city])
        tour.append(nearest)
        unvisited.remove(nearest)

    return tour, tsp_cost(tour, distances)

greedy_tour, greedy_cost = greedy_tsp(distances)

print(f"Greedy cost:  {greedy_cost:.2f}")
print(f"SA cost:      {best_cost:.2f}")
print(f"Mejora:       {((greedy_cost - best_cost) / greedy_cost * 100):.1f}%")

Por Qué Funciona SA

Ventajas de Simulated Annealing

1. Escapa de Óptimos Locales

  • Acepta empeoramientos ocasionales
  • Alta temperatura: exploración amplia
  • Baja temperatura: refinamiento local

2. Balance Exploración/Explotación

  • Inicio: explorar espacio de búsqueda
  • Final: converger a óptimo local

3. Fundamento Teórico

  • Con enfriamiento infinitamente lento → óptimo global
  • En práctica: soluciones muy buenas

4. Versátil

  • Aplicable a cualquier problema con:
    • Representación de estado
    • Función de costo
    • Función de vecindad

Comparación de Métodos Heurísticos para TSP:

MÉTODO 1: RANDOM SAMPLING (Muestreo Aleatorio)
  Estrategia: Generar tours aleatorios, quedarse con el mejor

  Pros: Simple, fácil de implementar
  Contras: Muy ineficiente, resultados mediocres

  Resultados típicos (50 ciudades):
    - 100 muestras: 40-50% sobre óptimo
    - 10,000 muestras: 25-35% sobre óptimo
    - Nunca encuentra cerca del óptimo

MÉTODO 2: HILL CLIMBING (Subida de Colina)
  Estrategia: Siempre aceptar mejoras, rechazar empeoramientos

  Pros: Converge rápido, fácil de implementar
  Contras: Se queda en primer óptimo local

  Resultados típicos (50 ciudades):
    - Mejora: 15-25% sobre óptimo
    - Problema: Depende mucho del punto inicial
    - Se estanca rápidamente

MÉTODO 3: SIMULATED ANNEALING
  Estrategia: Aceptar empeoramientos con probabilidad decreciente

  Pros: Escapa óptimos locales, robusto
  Contras: Requiere ajuste de parámetros

  Resultados típicos (50 ciudades):
    - Mejora: 3-8% sobre óptimo
    - Consistente en múltiples ejecuciones
    - Balance exploración/explotación

RESULTADOS EXPERIMENTALES (50 ciudades, óptimo = 1000):

Método              Costo Promedio  Desviación  Tiempo
Random (10k)        1350           ±150        5 seg
Hill Climbing       1180           ±80         1 seg
Simulated Annealing 1049           ±25         10 seg
Branch & Bound      1000           0           2 horas

Lección: SA encuentra soluciones mucho mejores que greedy/random, en tiempo razonable, sin quedar atrapado en óptimos locales.

Quiz

¿Por qué Simulated Annealing acepta soluciones peores ocasionalmente?


6. Parámetros de Simulated Annealing

Configuración Crítica

El éxito de SA depende del ajuste adecuado de sus parámetros.

Parámetros Clave

Guía de parámetros para SA
"""
CONFIGURACIÓN DE SIMULATED ANNEALING

1. TEMPERATURA INICIAL (T₀):
  Objetivo: Permitir exploración amplia inicial

  Heurística: Aceptar ~80-90% de empeoramientos al inicio

  Cálculo: T₀ = -Δ_promedio / ln(P_inicial)
  donde:
  - Δ_promedio = cambio promedio de costo
  - P_inicial = probabilidad inicial deseada (0.8-0.9)

  Típico: T₀ = 1.0 a 100.0 (normalizar costos)

2. TEMPERATURA FINAL (T_min):
  Objetivo: Criterio de parada

  Típico: T_min = 0.001 × T₀
  O: temperatura donde P(aceptar peor) < 0.01

3. FACTOR DE ENFRIAMIENTO (α):
  Objetivo: Controlar velocidad de descenso

  Fórmula: T\_{i+1} = α × T_i

  Típico: α = 0.85 - 0.99
  - α = 0.85: enfriamiento rápido (menos iteraciones)
  - α = 0.95: enfriamiento lento (más exploración)
  - α = 0.99: muy lento (mejor calidad, más tiempo)

  Trade-off: Calidad vs Tiempo

4. ITERACIONES POR TEMPERATURA:
  Objetivo: Alcanzar equilibrio térmico

  Heurística: Proporcional al tamaño del problema

  Típico:
  - Mínimo: 100 iteraciones
  - Ideal: n × log(n) donde n = tamaño
  - TSP-50: 200-500 iteraciones
  - TSP-100: 500-1000 iteraciones

5. ESQUEMAS DE ENFRIAMIENTO:

  a) GEOMÉTRICO (más común):
  T\_{i+1} = α × T_i

  b) LINEAL:
  T\_{i+1} = T_i - δ

  c) LOGARÍTMICO (teóricamente óptimo, muy lento):
  T_i = T₀ / log(1 + i)

  d) EXPONENCIAL:
  T_i = T₀ × e^(-i/τ)

EJEMPLO DE CONFIGURACIÓN:

# Para TSP pequeño (n ≤ 20):

T_initial = 10.0
T_min = 0.01
alpha = 0.95
iter_per_temp = 100

# Para TSP mediano (20 < n ≤ 100):

T_initial = 100.0
T_min = 0.001
alpha = 0.97
iter_per_temp = n \* 10

# Para TSP grande (n > 100):

T_initial = 1000.0
T_min = 0.0001
alpha = 0.99
iter_per_temp = n \* 5
"""

7. Otros Problemas NP-Completos

Aplicabilidad Universal

Las estrategias (aproximación y heurísticas) se aplican a TODOS los problemas NP-completos.

Estrategias por Problema Específico

Aproximaciones y heurísticas por problema

TSP (Traveling Salesman):

  • Aproximación: 2-aprox (TSP métrico), 1.5-aprox (Christofides)
  • Heurísticas: Simulated Annealing, Algoritmos genéticos, Lin-Kernighan

Knapsack (0/1 Mochila):

  • Aproximación: FPTAS: (1+ε)(1+\varepsilon)-aproximación
  • Heurísticas: Greedy por densidad, DP (pseudo-poli)

Vertex Cover:

  • Aproximación: 2-aproximación (matching)
  • Heurísticas: Greedy por grado, LP rounding

Graph Coloring:

  • Aproximación: O(n/logn)O(n/\log n)-aproximación
  • Heurísticas: Greedy (Welsh-Powell), Backtracking, SA

Set Cover:

  • Aproximación: O(logn)O(\log n)-aproximación (greedy)
  • Heurísticas: Greedy por costo-efectividad, LP rounding

Bin Packing:

  • Aproximación: First Fit (1.7-aprox), First Fit Decreasing (1.22-aprox)
  • Heurísticas: Best Fit, Next Fit

Job Scheduling:

  • Aproximación: List scheduling (2-aproximación)
  • Heurísticas: Shortest Job First, Simulated Annealing

8. Computación Cuántica y Límites

Mitos y Realidad

Mito: Las computadoras cuánticas resolverán problemas NP-completos en tiempo polinomial.

Realidad: NO se espera que las computadoras cuánticas resuelvan NP-completos en tiempo polinomial.

Lo que SÍ pueden hacer:

  • Algoritmo de Shor: Factorización en tiempo polinomial
  • Algoritmo de Grover: Búsqueda con speedup cuadrático
  • Simulación cuántica

Implicación para NP: Mejora de O(2n)O(2^n) a O(2n/2)O(2^{n/2}) (Grover), sigue siendo exponencial.

Factorización y Criptografía

Impacto cuántico en seguridad

Problema Clásico:

  • Factorizar n=p×qn = p \times q (p, q primos grandes)
  • Mejor algoritmo clásico: Exponencial en dígitos
  • Ejemplo: n de 2048 bits → años de cómputo

Algoritmo de Shor (Cuántico):

  • Factorizar n en tiempo polinomial: O((logn)3)O((\log n)^3)
  • Ejemplo: n de 2048 bits → minutos (con computadora cuántica)

Impacto en RSA:

RSA depende de dureza de factorización. Con computadora cuántica suficientemente grande:

  • ✗ RSA inseguro
  • ✗ Firmas digitales comprometidas
  • ✗ HTTPS vulnerable

Solución: Criptografía post-cuántica

  • ✓ Lattice-based cryptography
  • ✓ Code-based cryptography
  • ✓ Hash-based signatures

Estado Actual (2025):

  • Computadoras cuánticas: ~100 qubits
  • Necesario para romper RSA-2048: ~4000 qubits
  • Aún lejos de amenaza práctica
  • Investigación activa en post-cuántica

Generación de Claves:

El muestreo aleatorio funciona bien:

  1. Generar números primos grandes aleatorios
  2. Verificar primalidad (Miller-Rabin)
  3. Construir clave pública (n, e)
  4. Construir clave privada (d)

Eficiente incluso con computadoras clásicas.


9. Resumen de Estrategias

Guía de Selección

¿Qué método usar para tu problema NP-completo?

Instancia pequeña (n ≤ 20): → Búsqueda exhaustiva con poda (Branch and Bound) → Programación dinámica exponencial

Necesitas GARANTÍA de calidad: → Algoritmo de aproximación (si existe) → Factor constante del óptimo

Necesitas VELOCIDAD, sin garantía formal: → Heurística greedy → Backtracking con buenas podas

Instancia grande, necesitas CALIDAD: → Simulated Annealing → Algoritmos genéticos → Tabu Search

Tienes ESTRUCTURA ESPECIAL: → Explotar propiedades (planaridad, árboles, intervalos) → Puede haber algoritmo polinomial para subclase

Casos PROMEDIO son fáciles: → Algoritmo exacto con podas inteligentes → Funciona bien en práctica

Tabla Comparativa Final

MétodoTiempoCalidadGarantíaCuándo Usar
ExactoExponencialÓptima✅ 100%n ≤ 20
AproximaciónPolinomialα × óptimo✅ Factor αNecesitas garantía
GreedyRápidoVariable❌ NingunaPrimera aproximación
BacktrackingExponencialÓptima✅ 100%Caso promedio rápido
SA/GenéticoAjustableMuy buena❌ NingunaBalance calidad/tiempo

10. Cierre: Lecciones Prácticas

Conclusiones Clave

1. NP-Completo NO Significa Imposible

Significa: no hay algoritmo polinomial exacto (probablemente). Pero hay muchas alternativas prácticas.

2. Múltiples Herramientas Disponibles

  • Aproximación: garantías demostrables
  • Heurísticas: soluciones rápidas y buenas
  • Casos especiales: explotar estructura

3. Ajustar Expectativas

No busques perfección, busca solución útil:

  • 5% sobre óptimo suele ser suficiente
  • Tiempo razonable > perfección inalcanzable

4. Modelado Cuidadoso

Éxito de heurísticas depende de:

  • Buena representación de estado
  • Función de costo adecuada
  • Función de vecindad efectiva

5. Experimentación es Clave

Ajustar parámetros (SA, genéticos) requiere prueba y error en tus instancias específicas.

Quiz

Para un problema NP-completo con n=100, ¿qué método es más apropiado si necesitas alta calidad sin garantía formal?


Resumen Final

Has Dominado la Gestión de Intratabilidad

✓ Realidad de NP-Completos:

  • Demostrar dureza no es el final
  • Problemas reales requieren soluciones prácticas
  • Tres vías: caso promedio, heurísticas, aproximación

✓ Algoritmos de Aproximación:

  • Garantía de factor α del óptimo
  • Válida para TODAS las instancias
  • Ejemplo: 2-aproximación para Vertex Cover

✓ Simulated Annealing:

  • Heurística robusta y versátil
  • Escapa óptimos locales
  • Balance exploración/explotación
  • Requiere ajuste de parámetros

✓ Estrategia de Selección:

  • n pequeño → exacto con poda
  • Necesitas garantía → aproximación
  • Instancia grande + calidad → SA
  • Estructura especial → explotar

✓ Límites Fundamentales:

  • Computación cuántica NO resuelve NP-completos en poli
  • Pero puede impactar criptografía (Shor)
  • Buscar soluciones “suficientemente buenas”

¡Felicitaciones!

Has completado la gestión de intratabilidad. Ahora dominas:

  • Por qué NP-completitud no es el final del camino
  • Algoritmos de aproximación con garantías
  • Simulated Annealing: teoría y práctica
  • Configuración de parámetros para SA
  • Estrategias para cada tipo de problema
  • Limitaciones de computación cuántica
  • Cuándo usar cada método
  • Balance entre calidad, tiempo y garantías

Próximo paso: Explorar algoritmos genéticos, tabu search, optimización de colonia de hormigas, y técnicas metaheurísticas avanzadas. También: teoría de aproximación (PTAS, FPTAS) y límites de aproximabilidad.