Java的PriorityQueue显示了具有更高优先级的重复键的意外行为

时间:2019-09-13 08:45:35

标签: java heap priority-queue binary-heap

我试图使用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操作,在此期间,旧键可能破坏了堆栈不变式(具有更高的优先级,但在权重更新后可能被放错了位置),并且随后的任何堆积都在该旧节点上可能会使堆处于不一致状态。想法?????

1 个答案:

答案 0 :(得分:1)

问题在于您正在更改权重,但没有重新排序队列。在您的示例中,您添加了三个项目,其值分别为0、1和2。PriorityQueue在内部使用二进制堆。创建的堆是:

      1
    /   \
   2     3

然后将2节点的值更改为0,从而导致此无效堆:

      1
    /   \
   0     3

现在您有一个无效的堆。在二进制最小堆中,每个节点必须小于或等于其父节点。但是在这种情况下,10的父级。从现在开始,您在堆上执行的任何操作都将产生不可预测的结果。

Java PriorityQueue不能适应不断变化的节点优先级。它没有删除任意元素的方法,也无法让您访问后备存储,因此您无法编写自己的存储。如果您希望能够更改节点优先级,则必须找到一个支持它的优先级队列实现,或者自己动手。