Dado um conjunto de pilhas NXP com N sendo o número de pilhas e P como a capacidade de pilhas, como posso calcular o número mínimo de trocas necessárias para mover de algum nó no local A para algum local arbitrário B? Estou criando um jogo, e o objetivo final é classificar todas as pilhas para que elas tenham a mesma cor.
# 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']
]
Se eu quiser inserir um "B" de stacks[1][1]
tal forma stacks[1] = ["-", "B", "Y", "Y"]
. Como posso determinar o número mínimo de movimentos necessários para fazer isso?
Eu estive analisando várias abordagens, tentei algoritmos genéticos que geram todos os movimentos possíveis de um estado, os pontuam e depois seguem os melhores caminhos de pontuação, também tentei executar o algoritmo de Djikstra para encontrar o problema . Parece frustrantemente simples, mas não consigo descobrir uma maneira de fazê-lo funcionar em nada além de tempo exponencial. Há um algoritmo que está faltando que é aplicável aqui?
Editar
Escrevi esta função para calcular o número mínimo de movimentos necessários: stacks: Lista de caracteres que representam as peças da pilha, stacks [0] [0] é o topo da pilha [0] stack_ind: O índice do pilha em que a peça será adicionada a needs_piece: a peça que deve ser adicionada à pilha needs_index: o índice em que a peça deve ser localizada
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 teste em pilhas:
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
A implementação real do código não é a parte difícil, é determinar como implementar um algoritmo que resolve o problema com o qual estou lutando.
De acordo com a solicitação de @ YonIif, criei uma essência para o problema.
Quando executado, gera uma matriz aleatória das pilhas e escolhe uma peça aleatória que precisa ser inserida em uma pilha aleatória em um local aleatório.
A execução imprime algo desse formato no 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']
Atualização de status
Estou muito determinado a resolver esse problema de alguma forma .
Lembre-se de que existem maneiras de minimizar o número de casos, como os que @Hans Olsson mencionou nos comentários. Minha abordagem mais recente para esse problema foi desenvolver um conjunto de regras semelhantes às mencionadas e empregá-las em um algoritmo geracional.
Regras como:
Nunca inverta um movimento. Vá de 1-> 0 e 0-> 1 (não faz sentido)
Nunca mova uma peça duas vezes seguidas. Nunca mude de 0 -> 1 e depois 1 -> 3
Dado algum movimento das pilhas [X] para as pilhas [Y], depois um número de movimentos, então uma mudança das pilhas [Y] para as pilhas [Z], se as pilhas [Z] estiverem no mesmo estado em que estavam quando o movimento das pilhas [X] para as pilhas [Y], um movimento poderia ter sido eliminado movendo-se das pilhas [X] diretamente para as pilhas [Z]
Atualmente, estou abordando esse problema com uma tentativa de criar regras suficientes, para minimizar o número de movimentos "válidos", o suficiente para que uma resposta possa ser calculada usando um algoritmo geracional. Se alguém puder pensar em regras adicionais, eu estaria interessado em ouvi-las nos comentários.
Atualizar
Graças à resposta de @RootTwo, tive um grande avanço, que descreverei aqui.
Para o avanço
Defina a altura da meta como a profundidade em que a peça da meta deve ser colocada na pilha de destino.
Sempre que alguma peça do gol é colocada no índice <= stack_height - altura do gol, sempre haverá um caminho mais curto para a vitória através do método clear_path ().
Let S represent some solid Piece.
IE
Stacks = [ [R, R, G], [G, G, R], [-, -, -] ]
Goal = Stacks[0][2] = R
Goal Height = 2.
Stack Height - Goal Height = 0
Dada uma pilha como essa stack[0] = R
, o jogo é ganho.
GOAL
[ [ (S | -), (S | -), (S | -) ], [R, S, S], [(S | - ), (S | -), (S | -)] ]
Como é sabido que sempre há pelo menos espaços em branco stack_height disponíveis, o pior caso possível seria:
[ [ S, S, !Goal ], [R, S, S], [-, -, -]
Como sabemos que o gol não pode estar no destino do gol ou o jogo é vencido. Nesse caso, o número mínimo de movimentos necessários seriam os movimentos:
(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 uma pilha como essa stack[1] = R
, o jogo é ganho.
GOAL
[ [ (S | -), (S | -), S], [ (S | -), R, S], [(S | -), (S | -), (S | -)]
Sabemos que há pelo menos três espaços em branco disponíveis, portanto, o pior caso possível seria:
[ [ S, !Goal, S], [S, R, S], [ -, -, - ]
Nesse caso, o número mínimo de movimentos seriam os movimentos:
(1, 2), (0, 2), (0, 2), (1, 0)
Isso será válido para todos os casos.
Assim, o problema foi reduzido a um problema de encontrar o número mínimo de movimentos necessários para colocar a peça do gol na altura do gol ou acima dele.
Isso divide o problema em uma série de subproblemas:
Quando a pilha de destino tem sua peça acessível! = Peça da meta, determinando se existe um local válido para essa peça ou se a peça deve permanecer lá enquanto outra peça é trocada.
Quando a pilha de destino tem sua peça acessível == peça da meta, determinando se ela pode ser removida e colocada na altura desejada da meta ou se a peça deve permanecer enquanto outra é trocada.
Quando os dois casos acima exigirem que outra peça seja trocada, determine quais peças serão trocadas para aumentar para possibilitar que a peça atinja a altura da meta.
A pilha de destino sempre deve ter seus casos avaliados primeiro.
IE
stacks = [ [-, R, G], [-, R, G], [-, R, G] ]
Goal = stacks[0][1] = G
Verificar a pilha de metas primeiro leva a:
(0, 1), (0, 2), (1, 0), (2, 0) = 4 Moves
Ignorando a pilha de objetivos:
(1, 0), (1, 2), (0, 1), (0, 1), (2, 0) = 5 Moves