Учитывая набор стеков NXP, где N - это количество стеков, а P - емкость стеков, как я могу рассчитать минимальное количество перестановок, необходимое для перемещения из некоторого узла в местоположении A в какое-то произвольное местоположение B? Я разрабатываю игру, и конечной целью является сортировка всех стеков так, чтобы они были одного цвета.
# 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']
]
Если я хочу, чтобы вставить "B" в stacks[1][1]
такой, что stacks[1] = ["-", "B", "Y", "Y"]
. Как я могу определить минимальное количество ходов, необходимое для этого?
Я смотрел на несколько подходов, я попробовал генетические алгоритмы, которые генерируют все возможные ходы из состояния, оценивают их, а затем продолжают идти по лучшим путям скоринга, я также пытался запустить алгоритм Джикстры для поиска пути по проблеме , Это кажется невероятно простым, но я не могу придумать, как заставить его работать за исключением экспоненциального времени. Есть ли алгоритм, который мне не хватает, который применим здесь?
редактировать
Я написал эту функцию, чтобы вычислить минимальное количество необходимых ходов: stacks: список символов, представляющих фигуры в стеке, stacks [0] [0] является вершиной стека [0] stack_ind: индекс стек, к которому будет добавлен кусок. needs_piece: кусок, который должен быть добавлен в стек needs_index: индекс, в котором этот фрагмент должен быть расположен.
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")
Изменить: тестовые случаи на стеки:
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
Реальная реализация кода не та сложная часть, она определяет, как реализовать алгоритм, который решает проблему, с которой я борюсь.
По запросу @ YonIif я создал суть проблемы.
При запуске он генерирует случайный массив стеков и выбирает случайный фрагмент, который необходимо вставить в случайный стек в случайном месте.
Запуск его выводит что-то такого формата на консоль.
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']
Обновление статуса
Я очень решительно , чтобы решить эту проблему , так или иначе .
Имейте в виду, что есть способы минимизировать количество дел, таких как @Hans Olsson, упомянутые в комментариях. Мой самый последний подход к этой проблеме - разработать набор правил, аналогичных упомянутым, и использовать их в алгоритме поколений.
Правила, такие как:
Никогда не меняй ход. Перейти от 1-> 0, затем 0-> 1 (не имеет смысла)
Никогда не перемещайте фигуру дважды подряд. Никогда не двигайтесь от 0 -> 1, затем 1 -> 3
При некотором перемещении из стеков [X] в стеки [Y], затем через некоторое количество ходов, затем из стеков [Y] в стеки [Z], если стеки [Z] находятся в том же состоянии, в котором они находились при перемещении от стеков [X] к стекам [Y], ход можно было исключить, если перейти непосредственно из стеков [X] к стекам [Z]
В настоящее время я подхожу к этой проблеме, пытаясь создать достаточно правил, чтобы минимизировать количество «допустимых» ходов, достаточно, чтобы можно было рассчитать ответ с использованием алгоритма генерации. Если кто-нибудь может придумать дополнительные правила, мне было бы интересно услышать их в комментариях.
Обновить
Благодаря ответу @RootTwo у меня был небольшой прорыв, который я опишу здесь.
На прорыв
Определите высоту цели как глубину, которую необходимо поместить в целевую стопку.
Всякий раз, когда какая-либо часть ворот помещается в index <= stack_height - высота цели, всегда будет кратчайший путь к победе с помощью метода 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
При некотором стеке stack[0] = R
игра выиграна.
GOAL
[ [ (S | -), (S | -), (S | -) ], [R, S, S], [(S | - ), (S | -), (S | -)] ]
Поскольку известно, что они всегда имеют по крайней мере пустые места stack_height, наихудший возможный случай будет:
[ [ S, S, !Goal ], [R, S, S], [-, -, -]
Так как мы знаем, что цель не может быть в пункте назначения цели, или игра выиграна. В этом случае минимальное количество необходимых ходов будет:
(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
При некотором стеке stack[1] = R
игра выиграна.
GOAL
[ [ (S | -), (S | -), S], [ (S | -), R, S], [(S | -), (S | -), (S | -)]
Мы знаем, что доступно как минимум 3 пробела, поэтому наихудшим из возможных случаев будет:
[ [ S, !Goal, S], [S, R, S], [ -, -, - ]
В этом случае минимальное количество ходов было бы ходами:
(1, 2), (0, 2), (0, 2), (1, 0)
Это будет справедливо для всех случаев.
Таким образом, проблема была сведена к задаче нахождения минимального количества ходов, необходимого для размещения части ворот на высоте цели или выше.
Это разбивает проблему на ряд подзадач:
Когда у стека назначения есть свой доступный кусок! = Цель, определяющий, есть ли допустимое место для этого куска, или этот кусок должен оставаться там, пока другой кусок поменялся местами.
Когда у целевого стека есть доступная часть == цель, определяется, может ли она быть удалена и помещена на требуемую высоту цели, или должна ли часть оставаться, пока другой обменивается.
Когда в двух вышеупомянутых случаях требуется замена другой части, определяют, какие части следует поменять местами для увеличения, чтобы дать возможность целевому элементу достичь высоты цели.
В стеке назначения всегда должны сначала оцениваться случаи.
IE
stacks = [ [-, R, G], [-, R, G], [-, R, G] ]
Goal = stacks[0][1] = G
Проверка стека целей сначала приводит к:
(0, 1), (0, 2), (1, 0), (2, 0) = 4 Moves
Игнорирование стека голов:
(1, 0), (1, 2), (0, 1), (0, 1), (2, 0) = 5 Moves