13

我不知道如何简明扼要地描述目标,这可能就是为什么尽管搜索了很多,但我仍然无法找到适用的算法,但一张图片清楚地显示了它:

联锁项目

给定左侧网格中项目的状态,有没有人知道一种算法可以有效地找到右侧网格中显示的结束位置?在这种情况下,所有项目都“下降”“下降”,但方向当然是任意的。重点是:

  • 有一组任意形状的项目,但都由连续的正方形组成
  • 项目不能重叠
  • 所有项目都应在给定方向上移动最大距离,直到它们接触到墙壁,或者它们正在接触另一个项目,而该项目[...正在无限地接触另一个项目...]正在接触墙壁。

这不是作业,我不是学生。这是为了我自己对几何和编程的兴趣。我没有提到语言,因为它无关紧要。我可以用我正在为我正在从事的特定项目使用的语言实现任何算法。有用的答案可以用文字或代码来描述;重要的是想法。

这个问题可能会被抽象为某种依赖关系和松弛空间的图(在数学意义上),因此也许可以采用旨在最小化滞后时间的算法。

如果您不知道答案但即将尝试在现场编造一个算法,请记住可能存在循环依赖关系,例如互锁的粉红色(向后)“C”和蓝色“T”形状。T 的一部分在 C 之下,C 的一部分在 T 之下。如果互锁的项目通过几个部分的“循环”锁定,这将更加棘手。

适用算法的一些注意事项:由于我构建网格对象管理框架的方式,以下所有操作都非常容易和快速:

  • 枚举一块内的各个方块
  • 枚举所有片段
  • 找到占据整个网格中特定方格的棋子(如果有)

关于答案的注释: maniek 首先暗示了它,但 bloops 提供了一个绝妙的解释。我认为绝对关键是所有移动相同数量的部分都保持相互关系的洞察力,因此不必考虑这些关系

对于人口稀少的棋盘,另一个加速方法是移动所有棋子以消除完全空的行。计算空行并识别空行一侧(“上方”)的碎片非常容易。

最后一点:我确实实现了 bloops 描述的算法,并进行了一些特定于实现的修改。它工作得很好。

4

5 回答 5

10

理念

归纳定义冻结对象集如下:

  • 接触底部的物体被冻结。

  • 躺在冻结对象上的对象被冻结。

直观地说,冻结的物体已经到达了它们的最终位置。调用非冻结对象active

声明:所有活动物体可以同时向下下降一个单位。

证明:当然,一个活动对象不会撞到另一个活动对象,因为它们相对于彼此的相对位置不会改变。活动对象也不会撞到冻结的对象。如果是这样,那么活动对象实际上是冻结的,因为它位于一个冻结的对象上。这与我们的假设相矛盾。

我们的算法非常高级的伪代码如下:

while (there are active objects):
    move active objects downwards simultaneously until one of them hits a frozen object
    update the status (active/frozen) of each object

请注意,在 while 循环的每次迭代中至少有一个对象被冻结。此外,每个对象都会被冻结一次。这些观察将在分析实际算法的运行时复杂度时使用。

算法

我们使用时间的概念来提高大多数操作的效率。时间从 0 开始测量,活动物体每移动一个单位需要 1 个单位时间。观察到,当我们在 time 时t,所有当前在 time 活动的物体的位移t,正好是t向下的单位。

请注意,在每一列中,每个单元格的相对顺序是固定的。其含义之一是每个单元格最多可以直接阻止另一个单元格下落。这种观察可以用来有效地预测下一次碰撞的时间。我们最多也可以“处理”每个单元格一次。

我们对列进行索引,从 1 开始,从左到右递增;以及高度从 1 开始的行。为了便于实现,引入一个名为bottom- 的新对象,它是唯一一个最初被冻结的对象,由高度为 0 的所有单元格组成。

数据结构 为了高效的实现,我们维护以下数据结构:

  • A包含每个单元格的最终位移的关联数组。如果一个单元格是活动的,它的条目应该是,比如说,-1

  • 对于每一列k,我们维护S_k列中活动单元格的初始行数集k。我们需要能够支持对这个集合的后继查询和删除。我们可以使用 Van Emde Boas 树,并回答中的每个查询O(log log H);哪里H是网格的高度。或者,我们可以使用平衡二叉搜索树来执行这些操作O(log N);其中N是 column 中的单元格数k

  • 一个优先级队列Q,它将存储活动单元格及其密钥作为其未来碰撞的预期时间。同样,我们可以使用 vEB 树来获取查询时间,O(log log H)也可以使用优先级队列来获取O(log N)每个操作的时间。

执行

该算法的详细伪代码如下:

Populate the S_k's with active cells
Initialize Q to be an empty priority queue

For every cell b in bottom:
    Push Q[b] = 0

while (Q is not empty):
    (x,t) = Q.extract_min()     // the active cell x collides at time t
    Object O = parent_object(x)
    For every cell y in O:
        A[y] = t                // freeze cell y at displacement t
        k = column(y)           
        S_k.delete(y)
        a = S_k.successor(y)    // find the only active cell that can collide with y
        if(a != nil):
            // find the expected time of the collision between a and y
            // note that both their positions are currently t + (their original height) 
            coll_t = t + height(a) - height(y) - 1
            Push/update Q[a] = coll_t

可以通过查询A属于该对象的任何单元格的位移来获得任何对象的最终位置。

运行时间

我们只对每个细胞进行一次处理和冷冻。我们在冻结每个单元格的同时执行恒定数量的查找。我们假设parent_object查找可以在恒定时间内完成。整个算法的复杂性取决于O(N log N)O(N log log H)取决于我们使用的数据结构。这里,N是所有对象的单元格总数。

于 2012-07-10T12:05:26.127 回答
6

现在完全不同了:)

放在地上的每一块都是固定的。放在固定件上的每一件都是固定的。其余的可以移动。将未固定的部分向下移动 1 格,重复直到没有东西可以移动。

于 2012-07-10T11:01:47.690 回答
3

我还没有清除所有细节,但我认为以下似乎是一种有点系统的方法:

  1. 将整个图片视为图形,您需要的是所有顶点的拓扑排序 - 即项目。如果 A 的任何部分低于 B 的任何部分,则项目 A 应该在排序中的项目 B 之前。然后,当您对项目进行拓扑排序时,您可以按此顺序遍历它们并确定位置 - 作为所有项目在当前的下方已经有固定的位置。
  2. 为了能够进行拓扑排序,您需要一个无环图。然后,您可以使用强连通分量的一些算法来查找所有循环并将它们压缩为单个顶点。然后您可以对结果图执行顶级排序。
  3. 要在 SCC 中找到棋子的位置:首先将其视为一个大棋子并确定其最终位置。这将确定一些不能再移动的固定件。移除它们并对本 SCC 中的其余部分(如果有)重复相同的过程,以找出它们的最终位置。

第三部分是唯一看起来计算密集的部分——如果你有一个非常复杂的棋子结构,但它仍然应该比尝试一次移动一个网格的棋子更优化。

于 2012-07-10T00:56:10.000 回答
3

好的,这似乎如下 -

  • 我们一步一步地进行
  • 在每一步中,我们构建一个有向图,其顶点是对象集,其边如下 =>

1) 如果 x 和 y 是两个对象,那么如果 x 在 y 移动之前不能移动,则添加一条边 x->y。请注意,我们可以同时拥有 x->y 和 y->x 边。

2)还有一些物体因为它们在底部而不能再移动,所以我们将它们的顶点着色为蓝色。其余顶点为红色。

2)在有向图中,我们使用 Kosaraju 算法/Tarjan 算法等找到所有强连通分量。(如果您不熟悉 SCC,那么它们是非常强大的技术,您可以参考 Kosaraju 算法。)一旦我们得到 SCC我们将它们缩小到一个小顶点,即我们用单个顶点替换 SCC,同时保留所有外部(到 SCC)边。现在,如果 SCC 中的任何顶点为蓝色,则我们将新顶点着色为蓝色,否则为红色。这意味着如果一个对象不能在 SCC 中移动,那么没有一个可以。

3)你得到的图将是一个有向无环图,你可以进行拓扑排序。以顶部编号的递增顺序遍历顶点,只要您看到红色顶点并移动由顶点表示的对象。

继续此步骤,直到您无法再在步骤 3 中移动任何顶点。

如果两个对象 A 和 B 重叠,那么我们说它们相对于彼此不一致。为了证明正确性,请论证以下引理 1)“如果我移动一个 SCC,那么其中的任何对象都不会导致它们之间的不一致。” 2)“当我在步骤 3 中移动一个对象时,我不会导致不一致”

你现在面临的挑战将是正式证明正确性并找到合适的数据结构来有效地解决它。如果您需要任何帮助,请告诉我。

于 2012-07-10T04:24:43.390 回答
2

编辑了几次。我认为这就是您需要做的所有事情:

  1. 找到所有只能相互依赖的部分,并将它们组合成一个等效的更大部分(例如图片中的 theT和 backs C)。

  2. 遍历所有的碎片,在它们撞到东西之前将它们向下移动到最大的方向。重复直到没有任何动作。

于 2012-07-09T23:30:19.373 回答