3

I want to create a priority queue from an ArrayList with a custom comparator.

public PriorityQueue(Collection<? extends E> c, Comparator<? super E> comparator))

BUT there is no such constructor in PriorityQueue

I can see these constructors but haven't found a way to do it in O(n) like a heapify() would've done.

  1. Create a PQ with the constructor first and then add using addAll(...). addAll() would be O(nlogn) because addAll() internally calls add() for each element of the input collection.
PriorityQueue<Foo> priorityQueue = new PriorityQueue<>(inputList.size(), comparator);
priorityQueue.addAll(inputList); 
  1. Create PQ through Collection constructor: It doesn't have a way to set the comparator
PriorityQueue<Foo> priorityQueue = new PriorityQueue<>(inputList); 
zero
  • 701
  • 2
  • 8
  • 13
  • @JonnyHenly the addAll method adds each item returned by the iterator into the queue one at a time. It doesn't build the heap from the passed in collection all at once. I don't think the built-in PriorityQueue class supports O(n) construction. – rfheise Sep 07 '21 at 18:04
  • "Implementation note: this implementation provides O(log(n)) time for the enqueuing and dequeuing methods (offer, poll, remove() and add)" [link](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/PriorityQueue.html) -- you're right in thinking that. –  Sep 07 '21 at 18:05
  • @rfheise ahh you're right. The while loop in the [`siftUpComparable(int k, E x)`](http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/java/util/PriorityQueue.java#l651) method has to run on each addition. – Jonny Henly Sep 07 '21 at 18:08
  • 1
    That PriorityQueue.java file is GPLv2, if you're fine with that license, you can just copy it and make your own, add the constructor and make a `heapifyUsingComparator`. –  Sep 07 '21 at 18:11
  • @ rfheise IMO [public PriorityQueue(Collection extends E> c)](https://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/java/util/PriorityQueue.java#l189) is ```O(n)``` since it calls the ```heapify()``` internally @JonnyHenly my bad the size was inputList.size(). I've edited the code snippet. – zero Sep 07 '21 at 18:15
  • 1
    If you can make a heap on your own, you can also load it into the PQ by presenting it with a `SortedSet` interface. –  Sep 07 '21 at 18:20
  • 1
    IMO ```siftDown()``` is O(n) [Explained here](https://stackoverflow.com/a/18742428/2455546) – zero Sep 07 '21 at 18:20

1 Answers1

2

One approach is to subclass PriorityQueue and add the desired constructor. Then store a copy of the collection's array in the subclass and override PriorityQueue#toArray to return the stored collection's array. Finally, construct a PriorityQueue using the PriorityQueue(PriorityQueue<> pq) constructor which will end up calling PriorityQueue#heapify, which uses PriorityQueue#siftDown rather than PriorityQueue#siftUp. This will achieve the O(N) complexity you want.

Here's an example implementation using a builder class to encapsulate (hide) the PriorityQueue subclass:

/**
 * Builder class used to create a {@code PriorityQueue<E>} from a
 * {@code Collection<? extends E>} and a {@code Comparator<? super E>} in
 * {@code O(N)} time.
 * 
 * @see PriorityQueueBuilder#build(Collection, Comparator)
 */
public final class PriorityQueueBuilder {
    
    /** Don't subclass this class. */
    private PriorityQueueBuilder() {}
    
    /**
     * Creates a priority queue using a specified collection and comparator in
     * {@code O(N)} time.
     * 
     * @param <E> - the priority queue's type
     * @param c - the collection to create the priority queue with
     * @param comparator - the comparator to be used by the priority queue
     * @return a priority queue created from the specified collection and
     *         comparator
     * @throws NullPointerException if the specified collection is {@code null}
     */
    public static <E> PriorityQueue<E> build(Collection<? extends E> c, Comparator<? super E> comparator) {
        return new PriorityQueue<>(new NPriorityQueue<>(c, comparator));
    }
    
    
    /**
     * Temporary priority queue implementation used to create an actual
     * {@code PriorityQueue<E>}.
     * 
     * We extend {@code PriorityQueue} in order to use the 
     * {@code PriorityQueue(PriorityQueue<? extends E> pq)} constructor rather 
     * than its {@code PriorityQueue(Collection<? extends E> c)} constructor
     */
    private static class NPriorityQueue<E> extends PriorityQueue<E> {
        
        private Object[] nQueue;
        
        /**
         * Sets the comparator and makes a copy of the collections underlying
         * object array.
         */
        public NPriorityQueue(Collection<? extends E> c, Comparator<? super E> comparator) {
            super(comparator);
            
            nQueue = c.toArray();
        }
        
    

       /**
         * Returns an array containing all of the elements in this queue.
         * The elements are in no particular order.
         * 
         * We need to override this method in order to return our
         * {@code nQueue} rather than {@code PriorityQueue}'s
         * {@code queue}. {@code PriorityQueue} calls this method to construct
         * the queue in {@code initElementsFromCollection}.
         *
         * @return an array containing all of the elements in this queue
         */
        @Override
        public Object[] toArray() {
            // no need to return a copy, just pass along the reference
            return nQueue;
        }
        
    }
    
}

Here's a class to test the above approach:

public class Driver {
    
    public static final int RANGE_START = 1;
    public static final int RANGE_END = 10;
    
    /**
     * Entry point of the program.
     * @param args - command line arguments
     */
    public static void main(String[] args) {
        List<Integer> list = IntStream.rangeClosed(RANGE_START, RANGE_END)
                .boxed()
                .collect(Collectors.toList());
        
        Collections.shuffle(list);
        
        PriorityQueue<Integer> pq = PriorityQueueBuilder.build(list, getComparator());
        
        outputList(list);
        outputPriorityQueue(pq);
    }
    
    private static Comparator<Integer> getComparator() {
        return new Comparator<Integer>()
        {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o1 - o2;
            }
        };
    }
    
    private static void outputList(List<?> l) {
        System.out.print("         List: ");
        l.forEach(e -> System.out.printf("%2d ", e));
        System.out.println();
    }
    
    private static void outputPriorityQueue(PriorityQueue<?> pq) {
        System.out.print("PriorityQueue: ");
        while (!pq.isEmpty()) {
            System.out.printf("%2d ", pq.poll());
        }
        System.out.println();
    }
    
}

Here's the output of running the test class:

         List:  4  8  3  7 10  2  9  5  6  1 
PriorityQueue:  1  2  3  4  5  6  7  8  9 10 
Jonny Henly
  • 4,023
  • 4
  • 26
  • 43