Is there a not too complicated way how to implement a priority queue working with two criteria? The queue gets created with 2 Comparator
s and provides (besides add
) the operations poll1()
and poll2()
, where each removes and returns the smallest element according to the corresponding comparator.
Note that it has nothing in common with these two questions.
My use case is Branch and Bound Optimization. Expanding the candidate with the best bound is provably optimal when you're given an unlimited time. Assuming an unlimited time is provably wrong.
Strictly following this strategy often ends up with having no solution at all when the deadline comes. A simple band-aid is to direct the search towards a solution first and then switch to the best bound strategy. This is rather unsatisfactory as the first solution found can be of an arbitrary low quality.
That's why I'd like to use the two criteria queue: In one step, expand the best bound candidate and in another, expand the "best looking" candidate according to some heuristics.
Another possible use would be for pareto-optimization.
Use two TreeSet
s. Each set takes their respected comparator, and the add()
method adds the provided value to each set. poll1()
would acquire the first()
value in the first set, and remove the value from both sets. Similarly for poll2()
.
add()
, poll1()
and poll2()
will have Ο(log n).
An example implementation follows (the real thing probably needs more error checking and additional methods):
class Priority2Queue <E>
{
private TreeSet<E> q1;
private TreeSet<E> q2;
public Priority2Queue (Comparator<E> c1, Comparator<E> c2) {
q1 = new TreeSet<E>(c1);
q2 = new TreeSet<E>(c2);
}
public void add (E e) {
q1.add(e);
q2.add(e);
}
private E pollx (TreeSet<E> a, TreeSet<E> b) {
E top = a.first();
a.remove(top);
b.remove(top);
return top;
}
public E poll1 () { return pollx(q1, q2); }
public E poll2 () { return pollx(q2, q1); }
}
Use two priority queues.
One way to do this is tag an item for deletion when it's next in one's poll. If the other queue sees it, skip and move to next. I'm not sure the complexity of this, but it will depend on subtle questions such as how much longer one item remains in a queue than the other. I would suggest profiling for your actual situation.
If you care only about asymptotic complexity, i would go with @jxh's solution, which uses O(n)
memory and guaranteed time O(log(n))
per operation (that is the best possible) and requires very little coding.
However TreeSet
is backed by TreeMap
and that uses TreeEntry
for entries, and one entry will take total 32B
of memory, plus having 2 trees will result into 64B
of memory per element!
EDIT 2: The old answer was incorrect, I have moved it here if anyone wants to see it anyway. Follows (hopefully) correct answer.
The idea is really simple. Let's have 2 ordinary heaps, one sorted via the first comparator, the other one sorted via the second comparator. And with that 2 indexing arrays that will link the elements between the heaps.
For example if element X is in position 2 in heap 1 and in position 8 in heap 2, then the linking arrays will look like this:
linkFrom1To2[2] = 8;
linkFrom2To1[8] = 2;
So if you perform any element moving in either of the heaps, you have to update these arrays accordingly.
But now if you remove minimum from one heap, you know where that element was in the second heap, so you can remove it from there as well.
I have posted the full code here, this solution requires only 16B
per element and was even faster that the @jxh's solution in the tests I've done, but I used simple arrays with maximal capacity instead of some auto-growing structures like ArrayList
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With