Dado un conjunto de pilas NXP, siendo N la cantidad de pilas y P la capacidad de las pilas, ¿cómo puedo calcular la cantidad mínima de intercambios necesarios para pasar de un nodo en la ubicación A a una ubicación arbitraria B? Estoy diseñando un juego, y el objetivo final es ordenar todas las pilas para que sean del mismo color.
# Let "-" represent blank spaces, and assume the stacks are
stacks = [
['R', 'R', 'R', 'R'],
['Y', 'Y', 'Y', 'Y'],
['G', 'G', 'G', 'G'],
['-', '-', '-', 'B'],
['-', 'B', 'B', 'B']
]
Si quiero insertar una "B" en stacks[1][1]
tal que stacks[1] = ["-", "B", "Y", "Y"]
. ¿Cómo puedo determinar la cantidad mínima de movimientos necesarios para hacerlo?
He estado buscando múltiples enfoques, probé algoritmos genéticos que generan todos los movimientos posibles desde un estado, los puntúan y luego continúan por los mejores caminos de puntuación, también he intentado ejecutar el algoritmo de Djikstra para encontrar el camino en el problema . Parece frustrantemente simple, sin embargo, no puedo encontrar una manera de hacerlo funcionar en otra cosa que no sea el tiempo exponencial. ¿Me falta algún algoritmo que sea aplicable aquí?
Editar
He escrito esta función para calcular el número mínimo de movimientos necesarios: pilas: Lista de personajes que representan las piezas en la pila, las pilas [0] [0] es la parte superior de la pila [0] stack_ind: El índice de la apilar que la pieza se agregará a needs_piece: la pieza que se debe agregar a la pila needs_index: el índice donde se debe ubicar la pieza
def calculate_min_moves(stacks, stack_ind, needs_piece, needs_index):
# Minimum moves needed to empty the stack that will receive the piece so that it can hold the piece
num_removals = 0
for s in stacks[stack_ind][:needs_index+1]:
if item != "-":
num_removals += 1
min_to_unlock = 1000
unlock_from = -1
for i, stack in enumerate(stacks):
if i != stack_ind:
for k, piece in enumerate(stack):
if piece == needs_piece:
if k < min_to_unlock:
min_to_unlock = k
unlock_from = i
num_free_spaces = 0
free_space_map = {}
for i, stack in enumerate(stacks):
if i != stack_ind and i != unlock_from:
c = stack.count("-")
num_free_spaces += c
free_space_map[i] = c
if num_removals + min_to_unlock <= num_free_spaces:
print("No shuffling needed, there's enough free space to move all the extra nodes out of the way")
else:
# HERE
print("case 2, things need shuffled")
Editar: Casos de prueba en pilas:
stacks = [
['R', 'R', 'R', 'R'],
['Y', 'Y', 'Y', 'Y'],
['G', 'G', 'G', 'G'],
['-', '-', '-', 'B'],
['-', 'B', 'B', 'B']
]
Case 1: stacks[4][1] should be 'G'
Move 'B' from stacks[4][1] to stacks[3][2]
Move 'G' from stacks[2][0] to stacks[4][1]
num_removals = 0 # 'G' is directly accessible as the top of stack 2
min_to_unlock = 1 # stack 4 has 1 piece that needs removed
free_spaces = 3 # stack 3 has free spaces and no pieces need moved to or from it
moves = [[4, 3], [2, 4]]
min_moves = 2
# This is easy to calculate
Case 2: stacks[0][3] should be 'B'
Move 'B' from stacks[3][3] to stack[4][0]
Move 'R' from stacks[0][0] to stacks[3][3]
Move 'R' from stacks[0][1] to stacks[3][2]
Move 'R' from stacks[0][2] to stacks[3][1]
Move 'R' from stacks[0][3] to stacks[3][0]
Move 'B' from stacks[4][0] to stacks[0][3]
num_removals = 0 # 'B' is directly accessible
min_to_unlock = 4 # stack 0 has 4 pieces that need removed
free_spaces = 3 # If stack 3 and 4 were switched this would be 1
moves = [[3, 4], [0, 3], [0, 3], [0, 3], [0, 3], [4, 0]]
min_moves = 6
#This is hard to calculate
La implementación del código real no es la parte difícil, es determinar cómo implementar un algoritmo que resuelva el problema con el que estoy luchando.
Según la solicitud de @ YonIif, he creado una idea general del problema.
Cuando se ejecuta, genera una matriz aleatoria de las pilas y elige una pieza aleatoria que debe insertarse en una pila aleatoria en una ubicación aleatoria.
Ejecutarlo imprime algo de este formato en la consola.
All Stacks: [['-', '-', 'O', 'Y'], ['-', 'P', 'P', 'O'], ['-', 'P', 'O', 'Y'], ['Y', 'Y', 'O', 'P']]
Stack 0 is currently ['-', '-', 'O', 'Y']
Stack 0 should be ['-', '-', '-', 'P']
Actualización de estado
Estoy muy decidido a resolver este problema de alguna manera .
Tenga en cuenta que hay formas de minimizar el número de casos, como los que @Hans Olsson menciona en los comentarios. Mi enfoque más reciente para este problema ha sido desarrollar un conjunto de reglas similares a las mencionadas y emplearlas en un algoritmo generacional.
Reglas como:
Nunca reviertas un movimiento. Ir de 1-> 0 luego 0-> 1 (No tiene sentido)
Nunca muevas una pieza dos veces seguidas. Nunca moverse de 0 -> 1 luego 1 -> 3
Dado algún movimiento de las pilas [X] a las pilas [Y], luego cierto número de movimientos, luego un movimiento de las pilas [Y] a las pilas [Z], si las pilas [Z] están en el mismo estado que cuando se movió de las pilas [X] a las pilas [Y] ocurrió, un movimiento podría haberse eliminado moviéndose directamente de las pilas [X] a las pilas [Z]
Actualmente, me estoy acercando a este problema con un intento de crear suficientes reglas, para minimizar el número de movimientos "válidos", lo suficiente como para que se pueda calcular una respuesta utilizando un algoritmo generacional. Si alguien puede pensar en reglas adicionales, me interesaría escucharlas en los comentarios.
Actualizar
Gracias a la respuesta de @RootTwo, he tenido un pequeño avance, que esbozaré aquí.
Sobre el avance
Defina la altura de la portería como la profundidad en que la pieza de portería debe colocarse en la pila de destino.
Cada vez que una pieza de gol se coloca en index <= stack_height - altura de gol, siempre habrá un camino más corto hacia la victoria a través del método clear_path ().
Let S represent some solid Piece.
ES DECIR
Stacks = [ [R, R, G], [G, G, R], [-, -, -] ]
Goal = Stacks[0][2] = R
Goal Height = 2.
Stack Height - Goal Height = 0
Dada una pila tal que stack[0] = R
el juego se gana.
GOAL
[ [ (S | -), (S | -), (S | -) ], [R, S, S], [(S | - ), (S | -), (S | -)] ]
Como se sabe que siempre hay al menos espacios en blanco stack_height disponibles, el peor de los casos sería:
[ [ S, S, !Goal ], [R, S, S], [-, -, -]
Como sabemos que la pieza de gol no puede estar en el destino de gol o el juego está ganado. En cuyo caso, el número mínimo de movimientos necesarios serían los movimientos:
(0, 2), (0, 2), (0, 2), (1, 0)
Stacks = [ [R, G, G], [-, R, R], [-, -, G] ]
Goal = Stack[0][1] = R
Stack Height - Goal Height = 1
Dada una pila tal que stack[1] = R
el juego se gana.
GOAL
[ [ (S | -), (S | -), S], [ (S | -), R, S], [(S | -), (S | -), (S | -)]
Sabemos que hay al menos 3 espacios en blanco disponibles, por lo que el peor de los casos sería:
[ [ S, !Goal, S], [S, R, S], [ -, -, - ]
En este caso, el número mínimo de movimientos serían los movimientos:
(1, 2), (0, 2), (0, 2), (1, 0)
Esto será válido para todos los casos.
Por lo tanto, el problema se ha reducido a un problema de encontrar el número mínimo de movimientos necesarios para colocar la pieza de portería a la altura de la portería o por encima.
Esto divide el problema en una serie de subproblemas:
Cuando la pila de destino tiene su pieza accesible! = Pieza objetivo, determina si hay una ubicación válida para esa pieza, o si la pieza debe permanecer allí mientras se intercambia otra pieza.
Cuando la pila de destino tiene su pieza accesible == pieza de portería, determina si se puede quitar y colocar a la altura de portería requerida, o si la pieza debe permanecer mientras se intercambia otra.
Cuando los dos casos anteriores requieren que se intercambie otra pieza, determinar qué piezas intercambiar para aumentar y hacer posible que la pieza de meta alcance la altura de la meta.
La pila de destino siempre debe evaluar primero sus casos.
ES DECIR
stacks = [ [-, R, G], [-, R, G], [-, R, G] ]
Goal = stacks[0][1] = G
Comprobar la pila de objetivos primero conduce a:
(0, 1), (0, 2), (1, 0), (2, 0) = 4 Moves
Ignorando la pila de objetivos:
(1, 0), (1, 2), (0, 1), (0, 1), (2, 0) = 5 Moves