Técnicas utilizadas

Búsqueda por fuerza bruta sobre el espacio de estados utilizando BFS: se exploran las configuraciones alcanzables de la fila, generando nuevos estados mediante intercambios entre pares de posiciones.

La búsqueda se realiza por niveles, donde cada nivel representa una cantidad fija de swaps realizados. Por lo tanto, la primera configuración válida encontrada corresponde a la menor cantidad de intercambios necesarios.

Para evitar explorar repetidamente la misma configuración, se utiliza un set que almacena los estados ya visitados.

Idea de la solución

El algoritmo realiza una búsqueda exhaustiva por niveles utilizando BFS. En cada nivel se consideran todas las configuraciones alcanzables con una misma cantidad de intercambios. Desde cada configuración se prueban todos los pares de índices posibles como candidatos a intercambiar.

Como se exploran todas las configuraciones posibles de la fila (evitando únicamente volver a recorrer estados ya visitados), el algoritmo garantiza encontrar la cantidad mínima real de swaps, aunque a costa de explorar una cantidad de estados que crece muy rápidamente.

Código

def pareja(x):
    return x + 1 if x % 2 == 0 else x - 1
 
def es_valido(row):
    for i in range(0, len(row), 2):
        if pareja(row[i]) != row[i + 1]:
            return False
 
    return True
 
def fuerza_bruta(row):
    visitados = {tuple(row)}
    nivel = [row]
    swaps = 0
 
    while nivel:
        siguiente = []
 
        for actual in nivel:
            if es_valido(actual):
                return swaps
 
            for i in range(len(actual)):
                for j in range(i + 1, len(actual)):
                    nuevo = actual[:]
                    nuevo[i], nuevo[j] = nuevo[j], nuevo[i]
 
                    estado = tuple(nuevo)
 
                    if estado not in visitados:
                        visitados.add(estado)
                        siguiente.append(nuevo)
 
        nivel = siguiente
        swaps += 1

Traza de ejemplo

Con el vector: row = [0, 2, 1, 3]

Las parejas son y .

El algoritmo explora las configuraciones por niveles. Primero evalúa la configuración inicial, que corresponde a 0 intercambios. Como no es válida, genera todas las configuraciones posibles aplicando un único swap.

ConfiguraciónIntercambios¿Válida?Nota
[0, 2, 1, 3]0NOEstado inicial
[2, 0, 1, 3]1NOGenerada con
[1, 2, 0, 3]1NOGenerada con
[3, 2, 1, 0]1Generada con

Al llegar a [3, 2, 1, 0], las personas quedan sentadas junto a sus parejas: y .

Como la exploración se realiza por niveles, primero se revisan todas las configuraciones con 0 intercambios, luego las de 1 intercambio, luego las de 2, y así sucesivamente. Por eso, la primera configuración válida encontrada garantiza la mínima cantidad de swaps.

Resultado: 1

Complejidad

Temporal

Sea la longitud del arreglo, donde es la cantidad de parejas. El algoritmo explora por niveles las distintas configuraciones posibles de la fila. Para evitar recorrer indefinidamente los mismos estados, cada configuración visitada se almacena en un conjunto y no vuelve a explorarse.

En el peor caso pueden visitarse hasta configuraciones distintas, ya que cada una corresponde a una permutación de las personas en la fila. Desde cada estado pueden intentarse hasta intercambios posibles.

Por lo tanto, una cota superior para la complejidad temporal es .

Espacial

El conjunto de estados visitados puede llegar a almacenar hasta configuraciones distintas, cada una de longitud (todas las configuraciones pendientes de explorar).

Por lo tanto, una cota superior para la complejidad espacial es .

Cuándo usar esta técnica

Favorable cuando

  • La entrada es muy pequeña y se necesita una solución de referencia para validar otros algoritmos.
  • No se conoce (o no se puede demostrar) una propiedad de decisión local segura que habilite un enfoque más eficiente.

Limitaciones

  • Es inviable para los tamaños de entrada reales del problema: el crecimiento factorial hace que el tiempo de ejecución sea impracticable ya con pocas decenas de personas.
  • Explora combinaciones de swaps que nunca podrían formar parte de una solución óptima, haciendo trabajo redundante.

Comparación con las otras soluciones

Frente a branch and bound, la fuerza bruta no aprovecha ninguna poda: branch and bound explora el mismo espacio de búsqueda pero descarta ramas que ya no pueden mejorar la mejor solución encontrada, reduciendo el trabajo redundante sin perder la garantía de optimalidad.

Frente a greedy, la diferencia es aún mayor: greedy identifica que alcanza con corregir cada banco de asientos de forma local, evitando por completo la necesidad de explorar combinaciones, y resuelve el problema en tiempo lineal.

Referencias

  • Estructura de datos: set
  • Técnica de búsqueda: BFS