理念
归纳定义冻结对象集如下:
接触底部的物体被冻结。
躺在冻结对象上的对象被冻结。
直观地说,冻结的物体已经到达了它们的最终位置。调用非冻结对象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
是所有对象的单元格总数。