Враховуючи набір стеків 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"]
. Як я можу визначити мінімальну кількість рухів, необхідних для цього?
Я дивився на кілька підходів, я спробував генетичні алгоритми, які генерують усі можливі рухи від стану, оцінюють їх, а потім продовжують внизу найкращих балів, я також спробував запустити алгоритм Джикстра для визначення маршруту щодо проблеми . Це здається розчаровуючи простим, але я не можу придумати спосіб змусити його запуститись ні в що інше, ніж експоненційний час. Чи є у мене відсутній алгоритм, який застосовується тут?
Редагувати
Я написав цю функцію для обчислення мінімальної кількості необхідних рухів: стеки: Список списку символів, що представляють фігури в стеку, стеки [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']
Оновлення статусу
Я дуже налаштований якось вирішити цю проблему .
Майте на увазі, що існують способи мінімізувати кількість випадків, таких як випадки, про які Ханс Ольссон згадував у коментарях. Мій останній підхід до цієї проблеми полягає у розробці набору правил, подібних до згаданих, та використанню їх у алгоритмі поколінь.
Такі правила, як:
Ніколи не зворотний крок. Перехід від 1-> 0, потім 0-> 1 (не має сенсу)
Ніколи не рухайте шматок двічі поспіль. Ніколи не рухайтеся від 0 -> 1, потім 1 -> 3
З огляду на деякий перехід від стеків [X] до стеків [Y], то деяка кількість переміщень, то переміщення зі стеків [Y] в стеки [Z], якщо стеки [Z] знаходяться в тому ж стані, що і при переміщенні зі стеків [X] до стеків [Y] стався, переміщення можна було усунути, перемістившись зі стеків [X] безпосередньо в стеки [Z]
В даний час я підходжу до цієї проблеми зі спробою створити достатню кількість правил, щоб вона мінімізувала кількість "дійсних" рухів, достатньо, щоб відповідь можна було обчислити за допомогою генераційного алгоритму. Якщо хтось може придумати додаткові правила, мені було б цікаво почути їх у коментарях.
Оновлення
Завдяки відповіді @RootTwo у мене був невеликий прорив, який я окреслити тут.
На прорив
Визначте висоту цілі, оскільки глибина цільової деталі повинна бути розміщена в цільовому стеку.
Щоразу, коли якась частина мети буде розміщена в індексі <= 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