支持changePriority操作的Java PriorityQueue实现
我需要一个优先级队列的实现,该队列允许降低优先级操作,以便有效地实现Prim和Dijkstra算法 我已经编写了一个minHeap实现,使用HashMap存储堆中元素的索引。 我正在研究的问题需要计算使用Prim算法得到的最小生成树的总权重。虽然我的实现适用于最多200个节点的大多数测试用例,但对于许多更大的测试用例,我仍然得到不正确的输出 我的理解是,这种使用哈希映射的基于minheap的优先级队列实现是常见的,如果我的假设是错误的,请提供解决此问题的更合适的方法 我已经试着调试我的代码两天了,似乎唯一能修复它的方法就是将它与一个正常运行的实现进行比较。 因此,有人可以在java中使用HashMap共享这样一个PriorityQueue实现吗 尽管我已经尝试了很多测试用例,并且对于我自己可以跟踪到的所有测试用例(最多30个节点),到目前为止我已经得到了正确的答案,但是如果有一些特定的边界测试用例可以帮助我识别问题,那也将非常好 这是我的代码,我知道调试对其他任何人来说都是很耗时的,但是如果我错过了一些明显的东西,并且有更专业的人可以指出错误,那将非常感谢支持changePriority操作的Java PriorityQueue实现,java,graph-theory,priority-queue,prims-algorithm,min-heap,Java,Graph Theory,Priority Queue,Prims Algorithm,Min Heap,我需要一个优先级队列的实现,该队列允许降低优先级操作,以便有效地实现Prim和Dijkstra算法 我已经编写了一个minHeap实现,使用HashMap存储堆中元素的索引。 我正在研究的问题需要计算使用Prim算法得到的最小生成树的总权重。虽然我的实现适用于最多200个节点的大多数测试用例,但对于许多更大的测试用例,我仍然得到不正确的输出 我的理解是,这种使用哈希映射的基于minheap的优先级队列实现是常见的,如果我的假设是错误的,请提供解决此问题的更合适的方法 我已经试着调试我的代码两天了
import java.util.HashMap;
import java.util.NoSuchElementException;
public class Heap<Key extends Comparable<Key>> {
private Key[] heap;
private int maxN, n;
private HashMap<Key, Integer> map;
@SuppressWarnings("unchecked")
public Heap(int maxN) {
if(maxN < 0 ) throw new IllegalArgumentException();
this.maxN = maxN;
n = 0;
heap = (Key[]) new Comparable[maxN];
map = new HashMap<>(maxN);
}
boolean isEmpty() {
return n == 0;
}
boolean insert(Key e) {
if(n +1 > maxN) throw new IllegalArgumentException("maximum capacity reached " + maxN);
heap[n] = e;
map.put(e,n);
int i = n++;
while ( (i+1)/2 - 1 >= 0){
if ( e.compareTo(heap[(i+1)/2 - 1]) < 0 ) {
swap(i, (i+1)/2 - 1);
i = (i+1)/2 - 1;
}
else
break;
}
return true;
}
Key extractMin() {
if(n == 0) throw new NoSuchElementException("Priority queue underflow ");
Key min = heap[0];
swap(0, n-1);
map.remove(min);
n--;
int j = 0, s;
while(j <= (n/2)-1){
if(j == (n/2)-1 && n == (j+1)*2 )
s = (j+1)*2 - 1;
else
s = heap[(j+1)*2 - 1].compareTo(heap[(j+1)*2]) < 0 ? (j+1)*2 - 1 : (j+1)*2;
if(heap[j].compareTo(heap[s]) > 0 ){
swap(j, s);
j = s;
}
else break;
}
return min;
}
Key delete(Key e){
if(!map.containsKey(e)) throw new NoSuchElementException(e+"does not exist ");
int j = map.get(e), s;
Key del = e;
swap(j, n-1);
map.remove(e);
n--;
while( j <= n/2 - 1){
if(j == (n/2)-1 && n == (j+1)*2)
s = (j+1)*2 - 1;
else
s = heap[(j+1)*2 - 1].compareTo(heap[(j+1)*2]) < 0 ? (j+1)*2 - 1 : (j+1)*2;
if(heap[j].compareTo(heap[s]) > 0 ){
swap(j, s);
j = s;
}
else break;
}
return del;
}
boolean decreasePriority(Key e){
if(n == 0)
return insert(e);
if(map.containsKey(e))
delete(e);
return insert(e);
}
private void swap(int i, int j) {
Key t = heap[i];
heap[i] = heap[j];
heap[j] = t;
map.replace(heap[i], i);
map.replace(heap[j], j);
}
@Override
public String toString() {
String res = "[";
int i;
for (i = 0; i < n-1; i++){
res += heap[i] + ", ";
}
res += heap[i]+"]";
return res;
}
}
我认为问题在于你的删除方法。您的代码执行以下操作:
swap item to be removed with the last item in the heap
reduce heap count
push the new item down the heap
假设heap[j] 1
6 2
7 8 3
如果删除值为7的节点,则值3将替换该节点:
1
6 2
3 8
现在,您必须将其向上移动以生成有效堆:
1
3 2
6 8
这里的关键是,如果要替换的项与堆中的最后一个项位于不同的子树中,则替换节点可能比被替换节点的父节点小
如果要从堆的中间删除一个项,则需要将该项与最后一个项交换,然后必须检查替换节点是向上移动还是向下移动
<>你应该考虑的是,为了改变一个项目的优先级,你不必删除和重新添加。您所要做的就是更改优先级,然后适当调整项目的位置:向上或向下移动以将其置于新位置。我认为问题在于您的删除方法。您的代码执行以下操作:
swap item to be removed with the last item in the heap
reduce heap count
push the new item down the heap
假设heap[j] 1
6 2
7 8 3
如果删除值为7的节点,则值3将替换该节点:
1
6 2
3 8
现在,您必须将其向上移动以生成有效堆:
1
3 2
6 8
这里的关键是,如果要替换的项与堆中的最后一个项位于不同的子树中,则替换节点可能比被替换节点的父节点小
如果要从堆的中间删除一个项,则需要将该项与最后一个项交换,然后必须检查替换节点是向上移动还是向下移动
<>你应该考虑的是,为了改变一个项目的优先级,你不必删除和重新添加。您所要做的就是更改优先级,然后适当调整项目的位置:向上或向下移动以将其置于新位置。删除方法不正确,我对任意删除使用了与extractMin相同的过程,它没有考虑到我替换要删除的键的元素可能会在堆中上移或下移这一事实。使用swim和sink方法,我已经纠正了这个错误。另外,更改优先级不需要删除和插入,只需对swim和sink进行简单调用即可。仅当优先级降低时才进行swim,当优先级增加时才进行sink
import java.util.HashMap;
import java.util.NoSuchElementException;
public class Heap<Key extends Comparable<Key>> {
private Key[] heap;
private int maxN, n;
private HashMap<Key, Integer> map;
@SuppressWarnings("unchecked")
public Heap(int maxN) {
if(maxN < 0 ) throw new IllegalArgumentException();
this.maxN = maxN;
n = 0;
heap = (Key[]) new Comparable[maxN];
map = new HashMap<>(maxN);
}
boolean isEmpty() {
return n == 0;
}
boolean insert(Key e) {
if(n +1 > maxN) throw new IllegalArgumentException("maximum capacity reached " + maxN);
heap[n] = e;
map.put(e,n);
int i = n++;
swim(i);
return true;
}
Key extractMin() {
if(n == 0) throw new NoSuchElementException("Priority queue underflow ");
Key min = heap[0];
swap(0, n-1);
map.remove(min);
n--;
sink(0);
return min;
}
void delete(Key e){
if(!map.containsKey(e)) throw new NoSuchElementException(e+" does not exist ");
int j = map.get(e);
swap(j, n-1);
map.remove(e);
n--;
if(!swim(j))
sink(j);
}
void decreasePriority(Key e){
if(map.containsKey(e)){
int j = map.get(e);
swim(j);
}
else insert(e);
}
private void swap(int i, int j) {
Key t = heap[i];
heap[i] = heap[j];
heap[j] = t;
map.replace(heap[i], i);
map.replace(heap[j], j);
}
private boolean swim(int j){
boolean change = false;
int parent;
while( (parent = (j-1)/2 ) >= 0){
if(heap[j].compareTo(heap[parent]) < 0){
swap(j,parent);
j = parent;
change = true;
}
else break;
}
return change;
}
private void sink(int j){
while(j <= n/2 - 1){
int leftChild = j*2 + 1, rightChild = leftChild + 1, s;
if(rightChild >= n)
s = leftChild;
else
s = heap[leftChild].compareTo(heap[rightChild]) < 0 ? leftChild : rightChild;
if(heap[j].compareTo(heap[s]) > 0){
swap(j,s);
j = s;
}
else break;
}
}
@Override
public String toString() {
String res = "[";
int i;
for (i = 0; i < n-1; i++){
res += heap[i] + ", ";
}
res += heap[i]+"]";
return res;
}
}
编辑:小心使用类比较器。delete方法不正确,我对任意删除使用了与extractMin相同的过程,这没有考虑到我替换要删除的键的元素可能在堆上或堆下移动。使用swim和sink方法,我已经纠正了这个错误。另外,更改优先级不需要删除和插入,只需对swim和sink进行简单调用即可。仅当优先级降低时才进行swim,当优先级增加时才进行sink
import java.util.HashMap;
import java.util.NoSuchElementException;
public class Heap<Key extends Comparable<Key>> {
private Key[] heap;
private int maxN, n;
private HashMap<Key, Integer> map;
@SuppressWarnings("unchecked")
public Heap(int maxN) {
if(maxN < 0 ) throw new IllegalArgumentException();
this.maxN = maxN;
n = 0;
heap = (Key[]) new Comparable[maxN];
map = new HashMap<>(maxN);
}
boolean isEmpty() {
return n == 0;
}
boolean insert(Key e) {
if(n +1 > maxN) throw new IllegalArgumentException("maximum capacity reached " + maxN);
heap[n] = e;
map.put(e,n);
int i = n++;
swim(i);
return true;
}
Key extractMin() {
if(n == 0) throw new NoSuchElementException("Priority queue underflow ");
Key min = heap[0];
swap(0, n-1);
map.remove(min);
n--;
sink(0);
return min;
}
void delete(Key e){
if(!map.containsKey(e)) throw new NoSuchElementException(e+" does not exist ");
int j = map.get(e);
swap(j, n-1);
map.remove(e);
n--;
if(!swim(j))
sink(j);
}
void decreasePriority(Key e){
if(map.containsKey(e)){
int j = map.get(e);
swim(j);
}
else insert(e);
}
private void swap(int i, int j) {
Key t = heap[i];
heap[i] = heap[j];
heap[j] = t;
map.replace(heap[i], i);
map.replace(heap[j], j);
}
private boolean swim(int j){
boolean change = false;
int parent;
while( (parent = (j-1)/2 ) >= 0){
if(heap[j].compareTo(heap[parent]) < 0){
swap(j,parent);
j = parent;
change = true;
}
else break;
}
return change;
}
private void sink(int j){
while(j <= n/2 - 1){
int leftChild = j*2 + 1, rightChild = leftChild + 1, s;
if(rightChild >= n)
s = leftChild;
else
s = heap[leftChild].compareTo(heap[rightChild]) < 0 ? leftChild : rightChild;
if(heap[j].compareTo(heap[s]) > 0){
swap(j,s);
j = s;
}
else break;
}
}
@Override
public String toString() {
String res = "[";
int i;
for (i = 0; i < n-1; i++){
res += heap[i] + ", ";
}
res += heap[i]+"]";
return res;
}
}
编辑:小心使用类比较器。欢迎使用堆栈溢出。请学习如何使用堆栈溢出,并阅读如何提高问题的质量。然后你的问题包括你的源代码,这些源代码可以被其他人编译和测试。你的代码很难阅读。如果使用中间变量,这将非常有用。例如,s=heap[j+1*2-1]。比较堆[j+1*2]<0?j+1*2-1:j+1
*2; 如果使用中间变量leftChild和rightChild,则更易于阅读。此外,您的孩子和家长的计算是非标准的。大多数人使用的计算父项是i-1/2而不是i+1/2-1,左右子项是i*2+1和i*2+2而不是j+1*2-1和j+1*2。它们做同样的事情,但有一个更常用。@JimMischel我已经对我的代码进行了所有编辑,有什么方法可以进一步加快速度吗?欢迎使用堆栈溢出。请学习如何使用堆栈溢出,并阅读如何提高问题的质量。然后你的问题包括你的源代码,这些源代码可以被其他人编译和测试。你的代码很难阅读。如果使用中间变量,这将非常有用。例如,s=heap[j+1*2-1]。比较堆[j+1*2]<0?j+1*2-1:j+1*2;如果使用中间变量leftChild和rightChild,则更易于阅读。此外,您的孩子和家长的计算是非标准的。大多数人使用的计算父项是i-1/2而不是i+1/2-1,左右子项是i*2+1和i*2+2而不是j+1*2-1和j+1*2。它们做同样的事情,但有一个更常用。@JimMischel我已经对我的代码进行了所有编辑,有什么方法可以进一步加快速度吗?您可能需要重新考虑这个问题:ifj==n/2-1&&rightChild>=n。我的建议是消除j==n/2-1条件。如果rightChild>=n就足够了。此外,右子级总是比左子级多一个。因此,如果您同时计算这两个,您可以说,rightChild=leftChild+1。您可能需要重新考虑这个问题:ifj==n/2-1&&rightChild>=n。我的建议是消除j==n/2-1条件。如果rightChild>=n就足够了。此外,右子级总是比左子级多一个。如果你同时计算这两个,你可以说,rightChild=leftChild+1。