Étant donné un ensemble de piles NXP avec N étant le nombre de piles et P étant la capacité des piles, comment puis-je calculer le nombre minimum de swaps nécessaires pour passer d'un nœud de l'emplacement A à un emplacement arbitraire B? Je conçois un jeu, et l'objectif final est de trier toutes les piles afin qu'elles soient toutes de la même couleur.
# 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 je veux insérer un "B" à stacks[1][1]
tel point stacks[1] = ["-", "B", "Y", "Y"]
. Comment puis-je déterminer le nombre minimum de déplacements requis pour ce faire?
J'ai examiné plusieurs approches, j'ai essayé des algorithmes génétiques qui génèrent tous les mouvements possibles à partir d'un état, les notent, puis continuent sur les meilleurs chemins de notation, j'ai également tenté d'exécuter l'algorithme de Djikstra pour trouver le chemin du problème . Cela semble d'une simplicité frustrante, mais je ne peux pas trouver un moyen de le faire fonctionner dans autre chose que du temps exponentiel. Y a-t-il un algorithme qui me manque qui s'applique ici?
Éditer
J'ai écrit cette fonction pour calculer le nombre minimum de coups requis: piles: liste de la liste des personnages représentant les pièces de la pile, piles [0] [0] est le haut de la pile [0] stack_ind: l'index du pile que la pièce sera ajoutée à needs_piece: la pièce qui doit être ajoutée à la pile needs_index: l'index où la pièce doit être située
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")
Modifier: cas de test sur des piles:
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
L'implémentation réelle du code n'est pas la partie qui est difficile, c'est de déterminer comment implémenter un algorithme qui résout le problème avec lequel je me bats.
Conformément à la demande de @ YonIif, j'ai créé un résumé du problème.
Lorsqu'il s'exécute, il génère un tableau aléatoire des piles et choisit une pièce aléatoire qui doit être insérée dans une pile aléatoire à un emplacement aléatoire.
L'exécuter imprime quelque chose de ce format sur la console.
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']
Mise à jour du statut
Je suis très déterminé à résoudre ce problème d'une manière ou d'une autre .
Gardez à l'esprit qu'il existe des moyens de minimiser le nombre de cas, tels que ceux @Hans Olsson mentionnés dans les commentaires. Mon approche la plus récente de ce problème a été de développer un ensemble de règles similaires à celles mentionnées et de les utiliser dans un algorithme générationnel.
Des règles telles que:
N'inversez jamais un mouvement. Passez de 1-> 0 puis 0-> 1 (n'a aucun sens)
Ne déplacez jamais un morceau deux fois de suite. Ne jamais passer de 0 -> 1 puis 1 -> 3
Étant donné un certain mouvement des piles [X] vers les piles [Y], puis un certain nombre de mouvements, puis un mouvement des piles [Y] vers les piles [Z], si les piles [Z] sont dans le même état que lors du déplacement des piles [X] aux piles [Y] s'est produit, un mouvement aurait pu être éliminé en passant directement des piles [X] aux piles [Z]
Actuellement, j'aborde ce problème avec une tentative de créer suffisamment de règles, qu'il minimise le nombre de mouvements "valides", suffisamment pour qu'une réponse puisse être calculée à l'aide d'un algorithme générationnel. Si quelqu'un peut penser à des règles supplémentaires, je serais intéressé de les entendre dans les commentaires.
Mise à jour
Grâce à la réponse de @RootTwo, j'ai fait une petite percée, que je vais décrire ici.
Vers la percée
Définissez la hauteur du but comme la profondeur de la pièce de but doit être placée dans la pile de destination.
Chaque fois qu'un morceau de but est placé à index <= stack_height - hauteur du but, il y aura toujours un chemin le plus court vers la victoire via la méthode clear_path ().
Let S represent some solid Piece.
C'EST À DIRE
Stacks = [ [R, R, G], [G, G, R], [-, -, -] ]
Goal = Stacks[0][2] = R
Goal Height = 2.
Stack Height - Goal Height = 0
Étant donné une telle pile stack[0] = R
, le jeu est gagné.
GOAL
[ [ (S | -), (S | -), (S | -) ], [R, S, S], [(S | - ), (S | -), (S | -)] ]
Puisqu'il est connu qu'il y a toujours au moins des espaces vides stack_height disponibles, le pire des cas serait:
[ [ S, S, !Goal ], [R, S, S], [-, -, -]
Puisque nous savons que la pièce de but ne peut pas être dans la destination du but ou que le jeu est gagné. Dans ce cas, le nombre minimum de mouvements requis serait le nombre de mouvements:
(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
Étant donné une telle pile stack[1] = R
, le jeu est gagné.
GOAL
[ [ (S | -), (S | -), S], [ (S | -), R, S], [(S | -), (S | -), (S | -)]
Nous savons qu'il y a au moins 3 espaces vides disponibles, donc le pire des cas serait:
[ [ S, !Goal, S], [S, R, S], [ -, -, - ]
Dans ce cas, le nombre minimum de coups serait le nombre de coups:
(1, 2), (0, 2), (0, 2), (1, 0)
Cela vaut pour tous les cas.
Ainsi, le problème a été réduit à un problème de trouver le nombre minimum de mouvements requis pour placer la pièce de but à ou au-dessus à la hauteur du but.
Cela divise le problème en une série de sous-problèmes:
Lorsque la pile de destination a sa pièce accessible! = Pièce de but, déterminer s'il y a un emplacement valide pour cette pièce, ou si la pièce doit y rester pendant qu'un autre morceau est échangé.
Lorsque la pile de destination a sa pièce accessible == pièce de but, déterminer si elle peut être retirée et placée à la hauteur de but requise, ou si la pièce doit rester pendant qu'une autre est échangée.
Lorsque les deux cas ci-dessus nécessitent l'échange d'une autre pièce, déterminer les pièces à échanger afin d'augmenter afin de permettre à la pièce de but d'atteindre la hauteur du but.
La pile de destination doit toujours avoir ses cas évalués en premier.
C'EST À DIRE
stacks = [ [-, R, G], [-, R, G], [-, R, G] ]
Goal = stacks[0][1] = G
La vérification de la pile de buts conduit d'abord à:
(0, 1), (0, 2), (1, 0), (2, 0) = 4 Moves
Ignorer la pile de buts:
(1, 0), (1, 2), (0, 1), (0, 1), (2, 0) = 5 Moves