给定一组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']
]
如果我想在stacks[1][1]
处插入一个“B”,这样stacks[1] = ["-", "B", "Y", "Y"]
。我如何确定这样做所需的最少移动次数
我一直在研究多种方法,我尝试了遗传算法,从一个状态生成所有可能的移动,对它们进行评分,然后继续沿着最佳评分路径前进,我还尝试运行Djikstra的算法来寻找问题的路径。这看起来简单得令人沮丧,但我想不出一种方法,让它在指数时间以外的任何时间运行。是否有一个我缺少的适用于这里的算法
我编写此函数是为了计算所需的最小移动次数: 堆栈:表示堆栈中各部分的字符列表,堆栈[0][0]是堆栈[0]的顶部 stack_ind:工件将添加到的堆栈的索引 所需工件:应添加到堆栈中的工件 需要索引:工件应位于的索引
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的请求,我为这个问题创建了一个gist
当它运行时,它会生成一个随机堆栈数组,并选择一个需要插入随机堆栈中随机位置的随机片段
运行它会将这种格式的内容打印到控制台
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->;三,
假设从堆栈[X]移动到堆栈[Y],然后移动一定数量,然后从堆栈[Y]移动到堆栈[Z],如果堆栈[Z]与从堆栈[X]移动到堆栈[Y]时处于相同的状态,则可以通过从堆栈[X]直接移动到堆栈[Z]来消除移动
目前,我正试图通过创建足够的规则来解决这个问题,这样可以最大限度地减少“有效”移动的数量,这样就可以使用分代算法来计算答案。如果有人能想出更多的规则,我很想在评论中听到他们
多亏@RootTwo的回答,我有了一点突破,我将在这里概述一下
取得突破
将球门高度定义为球门片必须放置在球门中的深度 目标堆栈
每当某个球门被放置在索引处时<;=堆叠高度-目标高度, 通过clear_path()方法,总会有一条通往胜利的最短路径
Let S represent some solid Piece.
即
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 | -)] ]
因为众所周知,它们总是至少是堆叠高度的空格 如果可用,最坏的情况可能是:
[ [ 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
,游戏是won
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)
这将适用于所有情况
因此,该问题已被简化为一个问题,即找到最小数量的 将球门放置在球门高度或以上所需的移动
这将问题分解为一系列子问题:
当目标堆栈具有其可访问部分时!=球门, 确定该工件是否存在有效位置,或者工件是否应 在交换另一块时呆在那里
当目标堆栈具有其可访问片段==目标片段时, 确定是否可以将其移除并放置在所需的目标高度,或者 交换另一件时,该件应保留
当上述两种情况需要更换另一件时, 确定要交换哪些部件,以便增加以使 球门片达到球门高度
目标堆栈应始终首先计算其案例
即
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
虽然我还没有时间从数学上证明这一点,但我还是决定发布这篇文章;希望能有帮助。该方法是定义一个参数p,该参数随着良好的移动而减小,并在游戏结束时精确地达到零。在程序中,只考虑好的移动或中性移动(保持p不变),而忽略坏的移动(增加p)
那么什么是p?对于每列,将p定义为在该列中的所有颜色成为所需颜色之前仍需删除的块数。因此,假设我们希望红色块在最左边的列中结束(我稍后会回来讨论),假设底部有一个红色块,顶部有一个黄色块,顶部还有一个块,然后是一个空白。然后,该列的p=2(在全部为红色之前删除两个块)。计算所有列的p。对于最后应该为空的列,p等于其中的块数(所有块都应该为空)。当前状态的P是所有列的所有P的总和
当p=0时,所有列的颜色相同,一列为空,因此游戏结束
通过选择减少p(或至少不增加p)的移动,我们正在朝正确的方向移动,在我看来,这是最短路径算法的关键区别:Dijkstra不知道他在调查的每个顶点是否都朝着正确的方向移动
那么,我们如何确定每种颜色应该在哪里结束呢?基本上通过确定每种可能性的p。例如,从红/黄/绿/空开始,计算p,然后转到红/黄/空/绿,计算p,等等。取p最低的起始位置。这需要n!计算。对于n=8,这是40320,这是可行的。坏消息是,你必须检查所有最低p值相等的起始位置。好消息是你可以忘记剩下的
这里有两个数学上的不确定性。第一:有没有可能有一条较短的路径使用了错误的移动?似乎不太可能,我还没有找到反例,但我也没有找到证据。第二:当从非最佳起始位置(即非最低p)开始时,是否有可能比所有最佳起始位置的路径更短。再说一遍:没有反例,但也没有证据
一些实施建议。在每个列的执行过程中跟踪p并不困难,但当然应该这样做。应为每列保留的另一个参数是开放点的数量。如果为0,则此列可能暂时不接受任何块,因此可以不在循环中。当某列的p=0时,该列不符合pop的条件。对于每一个可能的pop,检查是否有一个好的移动,即降低总体p。如果有多个,请检查所有。如果没有,考虑所有中性的动作。
所有这些都将大大减少您的计算时间
我提出了两个选择,但没有一个能够及时解决案例2。第一个选项是使用带有字符串距离度量的*作为h(n),第二个选项是IDA*。我测试了许多字符串相似性度量,在我的方法中使用了smith waterman。我已更改了您的符号,以便更快地处理这个问题。我在每个数字的末尾添加了数字,以检查一个工件是否移动了两次
以下是我测试过的案例:
以下是A*代码:
以下是IDA*代码:
用法:
在您的评论中,有N个堆栈的容量为p,并且总是有p个空格。如果是这种情况,那么这个算法似乎可以在代码中的
else
子句中工作(即num_removals + min_to_unlock > num_free_spaces
):相关问题 更多 >
编程相关推荐