在其元素更改优先级时更新Java PriorityQueue

时间:2009-12-09 02:28:36

标签: java priority-queue

我正在尝试使用PriorityQueue使用Comparator订购对象。

这可以很容易地实现,但是对象类变量(比较器计算优先级)可能会在初始插入后发生变化。大多数人都提出了删除对象,更新值并再次重新插入的简单解决方案,因为这是优先级队列的比较器投入使用的时候。

除了在PriorityQueue周围创建一个包装类之外,还有更好的方法吗?

7 个答案:

答案 0 :(得分:33)

您必须删除并重新插入,因为队列的工作方式是在插入新元素时将其放入适当的位置。这比每次退出队列时找到最高优先级元素的选择要快得多。缺点是在插入元素后无法更改优先级。 TreeMap具有相同的限制(HashMap也是如此,当插入后其元素的哈希码发生变化时,它也会中断。)

如果要编写包装器,可以将比较代码从enqueue移动到dequeue。您不需要再排队入队时间(因为如果允许更改,它创建的顺序无论如何都不可靠。)

但是这会更糟糕,如果您更改任何优先级,您希望在队列上进行同步。由于您需要在更新优先级时添加同步代码,因此您可能只是出列并入队(在两种情况下都需要对队列的引用)。

答案 1 :(得分:10)

我不知道是否存在Java实现,但如果您正在更改键值,则可以使用Fibonnaci堆,其具有O(1)摊销成本减少 a堆中条目的键值,而不是普通堆中的O(log(n))。

答案 2 :(得分:5)

在值发生变化时,您是否可以直接控制

如果您知道值何时更改,您可以删除并重新插入(实际上这相当昂贵,因为删除需要在堆上进行线性扫描!)。 此外,您可以使用UpdatableHeap结构(尽管不是库存java)。本质上,这是一个跟踪hashmap中元素位置的堆。这样,当元素的优先级发生变化时,它可以修复堆。第三,你可以寻找一个同样的Fibonacci堆。

根据您的更新速率,每次线性扫描/快速排序/ QuickSelect也可能有效。特别是如果你有比pull更多的更新,这是要走的路。如果你有批量更新然后批量拉动操作,QuickSelect可能是最好的。

答案 3 :(得分:3)

要触发重新调整,请尝试以下操作:

if(!priorityQueue.isEmpty()) {
    priorityQueue.add(priorityQueue.remove());
}

答案 4 :(得分:1)

我尝试过的东西,到目前为止,它正在偷看,看看你正在改变的对象的引用是否与PriorityQueue的头部相同,如果是,那么你进行轮询( ),改变然后重新插入;否则你可以在没有轮询的情况下进行更改,因为当轮询头部时,无论如何堆都会堆积。

DOWNSIDE:这会更改具有相同优先级的对象的优先级。

答案 5 :(得分:0)

您可以实现的一个简单解决方案是,只需将该元素再次添加到优先级队列中。尽管它会占用更多空间,但不会改变提取元素的方式,但这也不会影响您的运行时间。

为了证明这一点,让我们在下面考虑dijkstra算法

public int[] dijkstra() {
int distance[] = new int[this.vertices];
int previous[] = new int[this.vertices];
for (int i = 0; i < this.vertices; i++) {
    distance[i] = Integer.MAX_VALUE;
    previous[i] = -1;
}
distance[0] = 0;
previous[0] = 0;
PriorityQueue<Node> pQueue = new PriorityQueue<>(this.vertices, new NodeComparison());
addValues(pQueue, distance);
while (!pQueue.isEmpty()) {
    Node n = pQueue.remove();
    List<Edge> neighbours = adjacencyList.get(n.position);
    for (Edge neighbour : neighbours) {
        if (distance[neighbour.destination] > distance[n.position] + neighbour.weight) {
            distance[neighbour.destination] = distance[n.position] + neighbour.weight;
            previous[neighbour.destination] = n.position;
            pQueue.add(new Node(neighbour.destination, distance[neighbour.destination]));
        }
    }
}
return previous;

}

我们的兴趣与时俱进 pQueue.add(new Node(neighbour.destination, distance[neighbour.destination])); 我没有通过删除并再次添加来更改特定节点的优先级,而只是添加具有相同值但不同优先级的新节点。 现在在提取时,我将始终首先获取该节点,因为我在此处实现了最小堆,并且总是在之后提取值大于此值(优先级较低)的节点,这样,当优先级降低时,所有相邻节点都将被放宽元素将被提取。

答案 6 :(得分:0)

无需自己重新实现优先级队列(因此​​仅使用 utils.PriorityQueue),您基本上有两种主要方法:

1) 取下并放回

删除元素,然后以新的优先级将其放回原处。这在上面的答案中有解释。删除一个元素是 O(n) 所以这种方法很慢。

2) 使用 Map 并将过时的项目保留在队列中

保持 HashMap 项 -> 优先级。地图的键是项目(没有优先级),地图的值是优先级。

使其与 PriorityQueue 保持同步(即每次从队列中添加或删除项目时,相应地更新 Map)。

现在,当您需要更改项目的优先级时,只需将具有不同优先级的相同项目添加到队列中即可。当你从队列中轮询一个项目时,检查它的优先级是否与你的地图中的相同。如果没有,那就放弃它并重新投票。

如果您不需要经常更改优先级,则第二种方法会更快。你的堆会更大,你可能需要轮询更多次,但你不需要找到你的项目。 “更改优先级”操作将是 O(f(n)log n*),其中 f(n)每个“更改优先级”操作的次数itemn* 堆的实际大小(即 n*f(n))。

我相信如果 f(n) 是 O(n/logn)(例如 f(n) = O(sqrt(n)),这比第一种方法快。< /p>

注意:在上面的解释中,优先级是指在您的比较器中使用的所有变量。此外,您的项目需要实现 equalshashcode,并且这两种方法都不应使用优先级变量。