我试图使用PriorityQueue类为优先级队列实现Djikstra算法的略微修改版本。我遇到一种奇怪的行为,找不到任何地方的解释。我对此行为有所怀疑,但想知道其他人对此有何看法。有人可以解释为什么PriorityQueue的行为如下:
由于标准算法涉及在“松弛”操作期间降低节点的权重,因此这意味着获取节点的句柄并降低其权重(我知道您使用哈希图来备份队列的方法,但这是一个实验)。此方法仅涉及将节点的另一个副本添加到PriorityQueue中。节点处理一次后,重复项将被忽略。然而,这种实现方式的问题在于,对于随后添加的重复项,PriorityQueue的行为未定义。这是显示相同内容的代码段:
import java.util.PriorityQueue;
public class PriorityQueueTest {
public static void main(String[] args) {
int[] weight = new int[]{1, 2, 3};
PriorityQueue<Integer> pq = new PriorityQueue<>(weight.length, (a, b) -> weight[a] - weight[b]);
for(int i=0; i<3; i++)
pq.add(i);
//change weight of node '1' from 2 to 0
weight[1] = 0;
//add duplicate node '1'. Note that this time weight 0 by the comparator.
pq.add(1);
while(!pq.isEmpty())
System.out.println(pq.remove());
}
}
随着节点'1'的权重降低并且其优先级随后提高,人们会期望按照以下顺序删除节点:
1
1
0
2
但是实际结果却不同:
0
1
1
2
还要注意,如果我只使用节点0和1(将上面的for循环更改为
for(int i=0; i<2; i++)
),然后按预期顺序
1
1
0
与实际订单相同!
您可以添加更多节点并更改其他节点的权重,有时这又会导致这种意外行为,具体取决于第一次复制后添加的节点数。
我怀疑是在添加重复项时,执行了heapify操作,在此期间,旧键可能破坏了堆栈不变式(具有更高的优先级,但在权重更新后可能被放错了位置),并且随后的任何堆积都在该旧节点上可能会使堆处于不一致状态。想法?????
答案 0 :(得分:1)
问题在于您正在更改权重,但没有重新排序队列。在您的示例中,您添加了三个项目,其值分别为0、1和2。PriorityQueue
在内部使用二进制堆。创建的堆是:
1
/ \
2 3
然后将2
节点的值更改为0
,从而导致此无效堆:
1
/ \
0 3
现在您有一个无效的堆。在二进制最小堆中,每个节点必须小于或等于其父节点。但是在这种情况下,1
是0
的父级。从现在开始,您在堆上执行的任何操作都将产生不可预测的结果。
Java PriorityQueue
不能适应不断变化的节点优先级。它没有删除任意元素的方法,也无法让您访问后备存储,因此您无法编写自己的存储。如果您希望能够更改节点优先级,则必须找到一个支持它的优先级队列实现,或者自己动手。