所以我有一个问题,我有一组带有两个字段的对象。我需要能够在快速时间(使用自定义比较器)获取集合的min对象。问题是,当我获得min对象集时,它会更改原始集中某些对象的字段。因此,当我完成min对象时,需要更改集合的顺序。
对我来说,使用带有自定义比较器的优先级队列似乎很明显,问题是,Java中的Priority Queue没有更改或减少键操作。我曾尝试在线查找其他实现优先级队列,该队列使用带减少键操作的比较器,但我找不到。如何处理这个问题,将非常感谢任何帮助?我可以使用其他数据结构吗?
以下是我对我所说的内容的改编。它有点hackish,我发现它无法正常工作,因为我在测试中发现在队列中添加元素的顺序会以某种方式影响排序优先队列本身。我不是在寻找这个特定代码的帮助,因为它很可能被抛出。
**
* A class representing a Fibonacci heap.
*
* @param T The type of elements to store in the heap.
* @author Keith Schwarz (htiek@cs.stanford.edu)
*/
public final class FibonacciHeap<T> {
/* In order for all of the Fibonacci heap operations to complete in O(1),
* clients need to have O(1) access to any element in the heap. We make
* this work by having each insertion operation produce a handle to the
* node in the tree. In actuality, this handle is the node itself, but
* we guard against external modification by marking the internal fields
* private.
*/
public static final class Entry<T> {
private int mDegree = 0; // Number of children
private boolean mIsMarked = false; // Whether this node is marked
private Entry<T> mNext; // Next and previous elements in the list
private Entry<T> mPrev;
private Entry<T> mParent; // Parent in the tree, if any.
private Entry<T> mChild; // Child node, if any.
private T mElem; // Element being stored here
private int mPriority; // Its priority
/**
* Returns the element represented by this heap entry.
*
* @return The element represented by this heap entry.
*/
public T getValue() {
return mElem;
}
/**
* Sets the element associated with this heap entry.
*
* @param value The element to associate with this heap entry.
*/
public void setValue(T value) {
mElem = value;
}
/**
* Returns the priority of this element.
*
* @return The priority of this element.
*/
public int getPriority() {
return mPriority;
}
/**
* Constructs a new Entry that holds the given element with the indicated
* priority.
*
* @param elem The element stored in this node.
* @param priority The priority of this element.
*/
private Entry(T elem, int priority) {
mNext = mPrev = this;
mElem = elem;
mPriority = priority;
}
}
/* Pointer to the minimum element in the heap. */
private Entry<T> mMin = null;
/* Cached size of the heap, so we don't have to recompute this explicitly. */
private int mSize = 0;
/**
* Inserts the specified element into the Fibonacci heap with the specified
* priority. Its priority must be a valid double, so you cannot set the
* priority to NaN.
*
* @param value The value to insert.
* @param priority Its priority, which must be valid.
* @return An Entry representing that element in the tree.
*/
public Entry<T> enqueue(T value, int priority) {
/* Create the entry object, which is a circularly-linked list of length
* one.
*/
Entry<T> result = new Entry<T>(value, priority);
/* Merge this singleton list with the tree list. */
mMin = mergeLists(mMin, result);
/* Increase the size of the heap; we just added something. */
++mSize;
/* Return the reference to the new element. */
return result;
}
/**
* Returns an Entry object corresponding to the minimum element of the
* Fibonacci heap, throwing a NoSuchElementException if the heap is
* empty.
*
* @return The smallest element of the heap.
* @throws NoSuchElementException If the heap is empty.
*/
public Entry<T> min() {
if (isEmpty())
throw new NoSuchElementException("Heap is empty.");
return mMin;
}
/**
* Returns whether the heap is empty.
*
* @return Whether the heap is empty.
*/
public boolean isEmpty() {
return mMin == null;
}
/**
* Returns the number of elements in the heap.
*
* @return The number of elements in the heap.
*/
public int size() {
return mSize;
}
/**
* Given two Fibonacci heaps, returns a new Fibonacci heap that contains
* all of the elements of the two heaps. Each of the input heaps is
* destructively modified by having all its elements removed. You can
* continue to use those heaps, but be aware that they will be empty
* after this call completes.
*
* @param one The first Fibonacci heap to merge.
* @param two The second Fibonacci heap to merge.
* @return A new FibonacciHeap containing all of the elements of both
* heaps.
*/
public static <T> FibonacciHeap<T> merge(FibonacciHeap<T> one, FibonacciHeap<T> two) {
/* Create a new FibonacciHeap to hold the result. */
FibonacciHeap<T> result = new FibonacciHeap<T>();
/* Merge the two Fibonacci heap root lists together. This helper function
* also computes the min of the two lists, so we can store the result in
* the mMin field of the new heap.
*/
result.mMin = mergeLists(one.mMin, two.mMin);
/* The size of the new heap is the sum of the sizes of the input heaps. */
result.mSize = one.mSize + two.mSize;
/* Clear the old heaps. */
one.mSize = two.mSize = 0;
one.mMin = null;
two.mMin = null;
/* Return the newly-merged heap. */
return result;
}
/**
* Dequeues and returns the minimum element of the Fibonacci heap. If the
* heap is empty, this throws a NoSuchElementException.
*
* @return The smallest element of the Fibonacci heap.
* @throws NoSuchElementException If the heap is empty.
*/
public Entry<T> dequeueMin() {
/* Check for whether we're empty. */
if (isEmpty())
throw new NoSuchElementException("Heap is empty.");
/* Otherwise, we're about to lose an element, so decrement the number of
* entries in this heap.
*/
--mSize;
/* Grab the minimum element so we know what to return. */
Entry<T> minElem = mMin;
/* Now, we need to get rid of this element from the list of roots. There
* are two cases to consider. First, if this is the only element in the
* list of roots, we set the list of roots to be null by clearing mMin.
* Otherwise, if it's not null, then we write the elements next to the
* min element around the min element to remove it, then arbitrarily
* reassign the min.
*/
if (mMin.mNext == mMin) { // Case one
mMin = null;
}
else { // Case two
mMin.mPrev.mNext = mMin.mNext;
mMin.mNext.mPrev = mMin.mPrev;
mMin = mMin.mNext; // Arbitrary element of the root list.
}
/* Next, clear the parent fields of all of the min element's children,
* since they're about to become roots. Because the elements are
* stored in a circular list, the traversal is a bit complex.
*/
if (minElem.mChild != null) {
/* Keep track of the first visited node. */
Entry<?> curr = minElem.mChild;
do {
curr.mParent = null;
/* Walk to the next node, then stop if this is the node we
* started at.
*/
curr = curr.mNext;
} while (curr != minElem.mChild);
}
/* Next, splice the children of the root node into the topmost list,
* then set mMin to point somewhere in that list.
*/
mMin = mergeLists(mMin, minElem.mChild);
/* If there are no entries left, we're done. */
if (mMin == null) return minElem;
/* Next, we need to coalsce all of the roots so that there is only one
* tree of each degree. To track trees of each size, we allocate an
* ArrayList where the entry at position i is either null or the
* unique tree of degree i.
*/
List<Entry<T>> treeTable = new ArrayList<Entry<T>>();
/* We need to traverse the entire list, but since we're going to be
* messing around with it we have to be careful not to break our
* traversal order mid-stream. One major challenge is how to detect
* whether we're visiting the same node twice. To do this, we'll
* spent a bit of overhead adding all of the nodes to a list, and
* then will visit each element of this list in order.
*/
List<Entry<T>> toVisit = new ArrayList<Entry<T>>();
/* To add everything, we'll iterate across the elements until we
* find the first element twice. We check this by looping while the
* list is empty or while the current element isn't the first element
* of that list.
*/
for (Entry<T> curr = mMin; toVisit.isEmpty() || toVisit.get(0) != curr; curr = curr.mNext)
toVisit.add(curr);
/* Traverse this list and perform the appropriate unioning steps. */
for (Entry<T> curr: toVisit) {
/* Keep merging until a match arises. */
while (true) {
/* Ensure that the list is long enough to hold an element of this
* degree.
*/
while (curr.mDegree >= treeTable.size())
treeTable.add(null);
/* If nothing's here, we're can record that this tree has this size
* and are done processing.
*/
if (treeTable.get(curr.mDegree) == null) {
treeTable.set(curr.mDegree, curr);
break;
}
/* Otherwise, merge with what's there. */
Entry<T> other = treeTable.get(curr.mDegree);
treeTable.set(curr.mDegree, null); // Clear the slot
/* Determine which of the two trees has the smaller root, storing
* the two tree accordingly.
*/
//TODO Test
Entry<T> min = (((Comparable<T>)other.mElem).compareTo(curr.mElem) < -1)? other : curr;
Entry<T> max = (((Comparable<T>)other.mElem).compareTo(curr.mElem) < -1)? curr : other;
/* Break max out of the root list, then merge it into min's child
* list.
*/
max.mNext.mPrev = max.mPrev;
max.mPrev.mNext = max.mNext;
/* Make it a singleton so that we can merge it. */
max.mNext = max.mPrev = max;
min.mChild = mergeLists(min.mChild, max);
/* Reparent max appropriately. */
max.mParent = min;
/* Clear max's mark, since it can now lose another child. */
max.mIsMarked = false;
/* Increase min's degree; it now has another child. */
++min.mDegree;
/* Continue merging this tree. */
curr = min;
}
/* Update the global min based on this node. Note that we compare
* for <= instead of < here. That's because if we just did a
* reparent operation that merged two different trees of equal
* priority, we need to make sure that the min pointer points to
* the root-level one.
*/
if (((Comparable<T>)curr.mElem).compareTo(mMin.mElem) < 1){
mMin = curr;
}
}
return minElem;
}
/**
* Decreases the key of the specified element to the new priority. If the
* new priority is greater than the old priority, this function throws an
* IllegalArgumentException. The new priority must be a finite double,
* so you cannot set the priority to be NaN, or +/- infinity. Doing
* so also throws an IllegalArgumentException.
*
* It is assumed that the entry belongs in this heap. For efficiency
* reasons, this is not checked at runtime.
*
* @param entry The element whose priority should be decreased.
* @param newPriority The new priority to associate with this entry.
* @throws IllegalArgumentException If the new priority exceeds the old
* priority, or if the argument is not a finite double.
*/
public void decreaseKey(Entry<T> entry, int newPriority) {
if (newPriority > entry.mPriority)
throw new IllegalArgumentException("New priority exceeds old.");
/* Forward this to a helper function. */
decreaseKeyUnchecked(entry, newPriority);
}
/**
* Deletes this Entry from the Fibonacci heap that contains it.
*!!!
* It is assumed that the entry belongs in this heap. For efficiency
* reasons, this is not checked at runtime.
*
* @param entry The entry to delete.
*/
public void delete(Entry<T> entry) {
/* Use decreaseKey to drop the entry's key to -infinity. This will
* guarantee that the node is cut and set to the global minimum.
*/
decreaseKeyUnchecked(entry, Integer.MIN_VALUE);
/* Call dequeueMin to remove it. */
dequeueMin();
}
/**
* Utility function which, given two pointers into disjoint circularly-
* linked lists, merges the two lists together into one circularly-linked
* list in O(1) time. Because the lists may be empty, the return value
* is the only pointer that's guaranteed to be to an element of the
* resulting list.
*
* This function assumes that one and two are the minimum elements of the
* lists they are in, and returns a pointer to whichever is smaller. If
* this condition does not hold, the return value is some arbitrary pointer
* into the doubly-linked list.
*
* @param one A pointer into one of the two linked lists.
* @param two A pointer into the other of the two linked lists.
* @return A pointer to the smallest element of the resulting list.
*/
private static <T> Entry<T> mergeLists(Entry<T> one, Entry<T> two) {
/* There are four cases depending on whether the lists are null or not.
* We consider each separately.
*/
if (one == null && two == null) { // Both null, resulting list is null.
return null;
}
else if (one != null && two == null) { // Two is null, result is one.
return one;
}
else if (one == null && two != null) { // One is null, result is two.
return two;
}
else { // Both non-null; actually do the splice.
/* This is actually not as easy as it seems. The idea is that we'll
* have two lists that look like this:
*
* +----+ +----+ +----+
* | |--N->|one |--N->| |
* | |<-P--| |<-P--| |
* +----+ +----+ +----+
*
*
* +----+ +----+ +----+
* | |--N->|two |--N->| |
* | |<-P--| |<-P--| |
* +----+ +----+ +----+
*
* And we want to relink everything to get
*
* +----+ +----+ +----+---+
* | |--N->|one | | | |
* | |<-P--| | | |<+ |
* +----+ +----+<-\ +----+ | |
* \ P | |
* N \ N |
* +----+ +----+ \->+----+ | |
* | |--N->|two | | | | |
* | |<-P--| | | | | P
* +----+ +----+ +----+ | |
* ^ | | |
* | +-------------+ |
* +-----------------+
*
*/
Entry<T> oneNext = one.mNext; // Cache this since we're about to overwrite it.
one.mNext = two.mNext;
one.mNext.mPrev = one;
two.mNext = oneNext;
two.mNext.mPrev = two;
/* Return a pointer to whichever's smaller. */
return ((Comparable<T>)one.mElem).compareTo(two.mElem) < 0? one : two;
}
}
/**
* Decreases the key of a node in the tree without doing any checking to ensure
* that the new priority is valid. This method does not modify and members in the
* entry objects element.
*
* @param entry The node whose key should be decreased.
* @param priority The node's new priority.
*/
private void decreaseKeyUnchecked(Entry<T> entry, int priority) {
/* First, change the node's priority. */
entry.mPriority = priority;
((GNode)entry.mElem).setInE(priority);
/* If the node no longer has a higher priority than its parent, cut it.
* Note that this also means that if we try to run a delete operation
* that decreases the key to -infinity, it's guaranteed to cut the node
* from its parent.
*/
if (entry.mParent != null && ((GNode)entry.mElem).compareTo((GNode)entry.mParent.mElem) <= 0)
cutNode(entry);
/* If our new value is the new min, mark it as such. Note that if we
* ended up decreasing the key in a way that ties the current minimum
* priority, this will change the min accordingly.
*/
//NPE fix If mMin in null, means we have only one entry left... and we swap
if (mMin == null || ((GNode)entry.mElem).compareTo((GNode)mMin.mElem) <= 0)
mMin = entry;
}
/**
* Cuts a node from its parent. If the parent was already marked, recursively
* cuts that node from its parent as well.
*
* @param entry The node to cut from its parent.
*/
private void cutNode(Entry<T> entry) {
/* Begin by clearing the node's mark, since we just cut it. */
entry.mIsMarked = false;
/* Base case: If the node has no parent, we're done. */
if (entry.mParent == null) return;
/* Rewire the node's siblings around it, if it has any siblings. */
if (entry.mNext != entry) { // Has siblings
entry.mNext.mPrev = entry.mPrev;
entry.mPrev.mNext = entry.mNext;
}
/* If the node is the one identified by its parent as its child,
* we need to rewrite that pointer to point to some arbitrary other
* child.
*/
if (entry.mParent.mChild == entry) {
/* If there are any other children, pick one of them arbitrarily. */
if (entry.mNext != entry) {
entry.mParent.mChild = entry.mNext;
}
/* Otherwise, there aren't any children left and we should clear the
* pointer and drop the node's degree.
*/
else {
entry.mParent.mChild = null;
}
}
/* Decrease the degree of the parent, since it just lost a child. */
--entry.mParent.mDegree;
/* Splice this tree into the root list by converting it to a singleton
* and invoking the merge subroutine.
*/
entry.mPrev = entry.mNext = entry;
mMin = mergeLists(mMin, entry);
/* Mark the parent and recursively cut it if it's already been
* marked.
*/
if (entry.mParent.mIsMarked)
cutNode(entry.mParent);
else
entry.mParent.mIsMarked = true;
/* Clear the relocated node's parent; it's now a root. */
entry.mParent = null;
}
}