I’ll give you the public signatures before the full code:
public class BinaryHeap<E>
{
public Node<E> insert(E userData, float cost);
public Node<E> peek();
public Node<E> pop();
public void remove(Node<E> node);
public void changeCost(Node<E> node, float newCost);
public int size();
public static class Node<E>
{
public E userData;
}
}
This is a data structure for the open list. You’ll probably want to adapt it to replace with the primitive int, but that’s a minor detail. The point is that the mapping from user data to the Node is done externally: if you don’t want to support removal then you can ignore it, but if you do then it’s up to the caller (in this case the A* implementation) to maintain the mapping from user data to Node. I’ve done it this way specifically to support your use case, because you can use a linearised array to do the mapping.
The code, plus a few unit tests to check that I haven’t bodged my parent/child index calculations. The changeCost method isn’t currently tested.
package com.akshor.pjt33.util;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
public class BinaryHeap<E>
{
@SuppressWarnings("unchecked")
private Node<E>[] heap = new Node[128];
private int size = 0;
public Node<E> insert(E userData, float cost)
{
Node<E> node = new Node<E>();
node.userData = userData;
node.cost = cost;
// Expand if necessary.
if (size == heap.length)
{
@SuppressWarnings("unchecked")
Node<E>[] newHeap = new Node[size << 1];
System.arraycopy(heap, 0, newHeap, 0, size);
heap = newHeap;
}
// Insert at end and bubble up.
heap[size] = node;
node.heapIdx = size;
upHeap(size++);
return node;
}
public Node<E> peek()
{
return heap[0];
}
public Node<E> pop()
{
Node<E> popped = heap[0];
heap[0] = heap[--size];
heap[size] = null;
if (size > 0) downHeap(0);
return popped;
}
public void remove(Node<E> node)
{
// This is what node.heapIdx is for.
heap[node.heapIdx] = heap[--size];
heap[size] = null;
if (size > node.heapIdx)
{
if (heap[node.heapIdx].cost < node.cost)
{
upHeap(node.heapIdx);
}
else
{
downHeap(node.heapIdx);
}
}
// Just as a precaution: should make stuff blow up if the node is abused.
node.heapIdx = -1;
}
public void changeCost(Node<E> node, float newCost)
{
float oldCost = node.cost;
node.cost = newCost;
if (newCost < oldCost)
{
upHeap(node.heapIdx);
}
else
{
downHeap(node.heapIdx);
}
}
public int size()
{
return size;
}
private void upHeap(int idx)
{
Node<E> node = heap[idx];
float cost = node.cost;
while (idx > 0)
{
int parentIdx = (idx - 1) >> 1;
Node<E> parent = heap[parentIdx];
if (cost < parent.cost)
{
heap[idx] = parent;
parent.heapIdx = idx;
idx = parentIdx;
}
else break;
}
heap[idx] = node;
node.heapIdx = idx;
}
private void downHeap(int idx)
{
Node<E> node = heap[idx];
float cost = node.cost;
while (true)
{
int leftIdx = 1 + (idx << 1);
int rightIdx = leftIdx + 1;
if (leftIdx >= size) break;
// We definitely have a left child.
Node<E> leftNode = heap[leftIdx];
float leftCost = leftNode.cost;
// We may have a right child.
Node<E> rightNode;
float rightCost;
if (rightIdx >= size)
{
// Only need to compare with left.
rightNode = null;
rightCost = Float.POSITIVE_INFINITY;
}
else
{
rightNode = heap[rightIdx];
rightCost = rightNode.cost;
}
// Find the smallest of the three costs: the corresponding node
// should be the parent.
if (leftCost < rightCost)
{
if (leftCost < cost)
{
heap[idx] = leftNode;
leftNode.heapIdx = idx;
idx = leftIdx;
}
else break;
}
else
{
if (rightCost < cost)
{
heap[idx] = rightNode;
rightNode.heapIdx = idx;
idx = rightIdx;
}
else break;
}
}
heap[idx] = node;
node.heapIdx = idx;
}
/**
* We assume that the user will be sensible with this! The design is aimed
* at people who have common sense and want efficiency.
*/
public static class Node<E>
{
/*package*/ float cost;
/*package*/ int heapIdx;
public E userData;
}
@Test
public void regressionTestOne()
{
BinaryHeap<String> heap = new BinaryHeap<String>();
heap.insert("A", 0);
heap.insert("B", 1);
assertEquals("A", heap.pop().userData);
heap.insert("D", 3);
heap.insert("L", 11);
assertEquals("B", heap.pop().userData);
heap.insert("K", 10);
heap.insert("C", 2);
assertEquals("C", heap.pop().userData);
heap.insert("E", 4);
assertEquals("D", heap.pop().userData);
heap.insert("I", 8);
heap.insert("H", 7);
assertEquals("E", heap.pop().userData);
heap.insert("F", 5);
assertEquals("F", heap.pop().userData);
heap.insert("G", 6);
heap.insert("J", 9);
assertEquals("G", heap.pop().userData);
assertEquals("H", heap.pop().userData);
assertEquals("I", heap.pop().userData);
assertEquals("J", heap.pop().userData);
assertEquals("K", heap.pop().userData);
assertEquals("L", heap.pop().userData);
assertEquals(0, heap.size());
}
@Test
public void downHeapLeft()
{
BinaryHeap<String> heap = new BinaryHeap<String>();
heap.insert("A", 0);
heap.insert("B", 1);
heap.insert("C", 2);
heap.insert("D", 3);
assertEquals("A", heap.pop().userData);
assertEquals("B", heap.pop().userData);
assertEquals("C", heap.pop().userData);
assertEquals("D", heap.pop().userData);
assertEquals(0, heap.size());
}
@Test
public void downHeapRight()
{
BinaryHeap<String> heap = new BinaryHeap<String>();
heap.insert("A", 0);
heap.insert("C", 2);
heap.insert("B", 1);
heap.insert("D", 3);
assertEquals("A", heap.pop().userData);
assertEquals("B", heap.pop().userData);
assertEquals("C", heap.pop().userData);
assertEquals("D", heap.pop().userData);
assertEquals(0, heap.size());
}
@Test
public void testRemovalDownheap()
{
BinaryHeap<String> heap = new BinaryHeap<String>();
heap.insert("A", 0);
Node<String> nodeB = heap.insert("B", 1);
heap.insert("C", 2);
heap.insert("D", 3);
heap.insert("E", 4);
heap.insert("F", 5);
heap.remove(nodeB);
assertEquals("A", heap.pop().userData);
assertEquals("C", heap.pop().userData);
assertEquals("D", heap.pop().userData);
assertEquals("E", heap.pop().userData);
assertEquals("F", heap.pop().userData);
assertEquals(0, heap.size());
}
@Test
public void testRemovalUpheap()
{
BinaryHeap<String> heap = new BinaryHeap<String>();
heap.insert("A", 0);
heap.insert("B", 3);
heap.insert("C", 1);
Node<String> nodeD = heap.insert("D", 4);
heap.insert("E", 5);
heap.insert("F", 6);
heap.insert("G", 2);
// Heap currently looks like
// A:0
// / \
// B:3 C:1
// / \ / \
// D:4 E:5 F:6 G:2
//
// If we delete D, swapping G up, then we get:
// A:0
// / \
// B:3 C:1
// / \ /
// G:2 E:5 F:6
// Assuming we don't upheap, two pops will remove A and C (swapping and
// down-heaping F and E) and the third will remove B. If we tried to use
// the minimum test case (F:2, no G) then although we would have an
// inconsistent intermediate state we would pop out in the correct
// order, because popping() C would put F to root and downheap it.
heap.remove(nodeD);
assertEquals("A", heap.pop().userData);
assertEquals("C", heap.pop().userData);
assertEquals("G", heap.pop().userData);
assertEquals("B", heap.pop().userData);
assertEquals("E", heap.pop().userData);
assertEquals("F", heap.pop().userData);
assertEquals(0, heap.size());
}
}