48

Do you know of a popular library (Apache, Google, etc, collections) which has a reliable Java implementation for a min-max heap, that is a heap which allows to peek its minimum and maximum value in O(1) and to remove an element in O(log n)?

nbro
  • 15,395
  • 32
  • 113
  • 196
Yuval Adam
  • 161,610
  • 92
  • 305
  • 395

7 Answers7

40

From Guava: MinMaxPriorityQueue.

Sleiman Jneidi
  • 22,907
  • 14
  • 56
  • 77
Louis Wasserman
  • 191,574
  • 25
  • 345
  • 413
  • 1
    Especially since the original question asked about google-collections, which is now Guava. – Louis Wasserman Feb 02 '12 at 21:50
  • the only small disadvantage is lack of standard `Deque` implementation – Alex Salauyou Jun 29 '16 at 09:33
  • It doesn't satisfy the Deque contract, so that is working as intended. – Louis Wasserman Jun 29 '16 at 17:54
  • could you please clarify, why? The only difficulty I'm able to see is implementing `Deque#removeFirstOccurrence()` and `removeLastOccurence()` due to unstable nature of min-max heap – Alex Salauyou Jun 29 '16 at 20:37
  • 1
    No, the issue is that deques have to stay in the order you put in the elements. For example, the documentation of `Deque` specifies that when it is used like a queue, FIFO behavior results. `MinMaxPriorityQueue` could technically implement the methods, but it could not satisfy the contract. – Louis Wasserman Jun 29 '16 at 20:39
  • 1
    After having had a brief look at the Guava's `MinMaxPriorityQueue` source code, I realized that it does not implement a "real" _min-max heap_ (which very few people know about), but rather it simply implements a double-ended priority queue using two heaps, a min-heap and a max-heap, which are put together. – nbro Mar 14 '17 at 22:50
38

Java has good tools in order to implement min and max heaps. My suggestion is using the priority queue data structure in order to implement these heaps. For implementing the max heap with priority queue try this:

import java.util.PriorityQueue;

public class MaxHeapWithPriorityQueue {

    public static void main(String args[]) {
    // create priority queue
    PriorityQueue<Integer> prq = new PriorityQueue<>(Collections.reverseOrder());

    // insert values in the queue
    prq.add(6);
    prq.add(9);
    prq.add(5);
    prq.add(64);
    prq.add(6);

    //print values
    while (!prq.isEmpty()) {
        System.out.print(prq.poll()+" ");
    }
    }

}

For implementing the min heap with priority queue, try this:

import java.util.PriorityQueue;

public class MinHeapWithPriorityQueue {

    public static void main(String args[]) {
        // create priority queue
        PriorityQueue< Integer > prq = new PriorityQueue <> ();

        // insert values in the queue
        prq.add(6);
        prq.add(9);
        prq.add(5);
        prq.add(64);
        prq.add(6);

        //print values
        while (!prq.isEmpty()) {
            System.out.print(prq.poll()+" ");
        }
    }

}

For more information, please visit:

Mohammad
  • 6,024
  • 3
  • 22
  • 30
  • 19
    It's more convenient to use [Collections.reverseOrder()](https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#reverseOrder--) as an argument to PriorityQueue in MaxHeap class. – Michael Berdyshev Feb 27 '17 at 15:53
  • What do you mean by more convenient? Would you please explain more? – Mohammad Mar 02 '17 at 14:49
  • I think that _reverseOrder_ method is more clear to read and understand what it does comparing to `(x,y) -> y-x` – Michael Berdyshev Mar 02 '17 at 21:56
  • Michael, the way that I did it, is the suggested way by Oracle. Oracle suggested using Comparator. Please look at this link: https://docs.oracle.com/javase/8/docs/api/java/util/PriorityQueue.html – Mohammad Mar 03 '17 at 03:11
  • [Collections.reverseOrder()](https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#reverseOrder--) also returns a Comparator. So it can be used in the [PriorityQueue constructor](https://docs.oracle.com/javase/8/docs/api/java/util/PriorityQueue.html#PriorityQueue-java.util.Comparator-). Both methods work. But for me _reverseOrder_ is more evident. That's all. – Michael Berdyshev Mar 03 '17 at 15:23
  • 2
    For this part, I used lambda expression (Java 8). However, this is based on preference. Your suggestion is also good. I appreciate your alternative. – Mohammad Mar 05 '17 at 02:11
  • 4
    In the Lambda Expression, the comparison-by-subtraction trick could be easily broken due to [integer overflow](https://stackoverflow.com/questions/2728793/java-integer-compareto-why-use-comparison-vs-subtraction). Plain comparison is suggested: `(x, y) -> x < y ? -1 : x == y ? 0 : 1` – Ruifeng Ma Apr 22 '18 at 10:07
  • @Mohammad , this is not what is required in the question. your answer is about min heap (or max heap). the Question is about min-max heap which is a data structure that combine the positive points of min and max heap at same time to allow these operation : 1- get min in O(1), get max in O(1). delete min in O(log n) , delete max in O(log n) .... etc. for more detail about this please see [min-max paper](http://akira.ruc.dk/~keld/teaching/algoritmedesign_f03/Artikler/02/Atkinson86.pdf) – ibra Oct 26 '20 at 13:08
  • @ibra The question asks about a data structure which can find the min and max in O(1) and remove an element in O(lgn). I am suggestion to use two heaps. One minheap and one maxheap with the same elements as a class Element. In this case, when you remove an element from maxheap, you can also remove it from the minheap. The space complexity of my solution is O(n) because we need to keep track of elements in the heap ds X2 (min and max). Also, the removing and adding elements will be O(lgn) (X2 which will be removed since 2 is a constant). – Mohammad Nov 01 '20 at 21:15
  • @Mohammad, Using two heaps (min heap + max heap) doesn't work. **The problem is deleting the same element is O(log n) from first heap, but it is O(n) from the second heap**. Example: **delete the min value** which is **in the min heap** at the top, and after we rebalance the heap *this take O(log n)**. Now we have to **delete the same element form Max heap** (Max at the top), we have to search for the min value and delete it and rebalance the heap **this take (0.5 n + log n) which is O(n)**. you ended with O(log n) + O(n) which is O(n) for deletion. – ibra Nov 02 '20 at 12:57
  • @ibra if you keep track of the objects in a map and then you put them in both heaps, then when you remove you can also remove it from the other heap (you remove one object from two heaps). This is a nice feature of Java. Almost everything is an object. This problem can be solved by two heaps. Basically, the min-max heap is a priority queue that allows you to remove elements from both sides min or max sides. You can implement this with basic data structures. – Mohammad Nov 02 '20 at 23:35
  • @Mohammad, So **you agree that the use of only the min heap + max heap alone doesn't solve the problem.** I suggest to update your post with the complete solution. **Using Map with the two min heap and max heap, doesn't solve the problem either. ** 1) Problem of duplicate values, 2) When you delete an object, it's reference will be null at the two heap, you have to find them in the two heap (one at the top, and the other search for it...) in order to balance the two heaps. – ibra Nov 03 '20 at 16:31
  • @Mohammad, Possible solution pbm 1) you can use [multimap](https://stackoverflow.com/questions/1062960/map-implementation-with-duplicate-keys)); pbm 2) for each value, you have to keep also their references in the two heaps (or index when using array) to balance the two heap. So, whith n values, you will have 3n (value, ref in min heap, and ref in max heap). **You will end with complicated solution with 5n space**, and I don't know if it work in all cases. The simplest and the efficient solution is the min-max heap as a commented before. good luck. – ibra Nov 03 '20 at 16:31
  • @ibra 1n or 2n or 10,000n is the O(n) space complexity. You need a data structure with complexity of O(n) for this problem. How do you think Oracle creates different complicated data structures? They mix simple ones and make the more complicated ones. Please be creative in solving problems. Not always Oracle needs to give us exactly what we want. Sometimes it is a good practice to even implement your own data structures like min or max heap. Also, for the Big O notation, please read https://en.wikipedia.org/wiki/Big_O_notation . The constants are getting removed always. – Mohammad Nov 04 '20 at 00:47
  • Yes indeed, constant are removed from the asymptotic complexity, you are right :). My points are, 1) using two heap alone doesn't solve the problem; 2) adding other data structure, may solve the problem; the solution will be complicated. 3) **From practical perspective**, we can compare the complexity with the constant like in the [min-max paper](http://akira.ruc.dk/~keld/teaching/algoritmedesign_f03/Artikler/02/Atkinson86.pdf) in order to show the difference (example: in parallelism we say: we achieve a speed up of 20x faster....) 4) We choose the simplest solution if it exit. – ibra Nov 04 '20 at 13:14
  • **Dear @Mohammad , thank you very much for all your responses, I really appreciate that**. And please don't be angry, my goal is only to discuss the solution in your post of using two heaps. Thank you very much :) and good luck. ;) – ibra Nov 04 '20 at 13:15
28

Instead of a max-min heap, could you use two instances of a java.util.PriorityQueue containing the same elements? The first instance would be passed a comparator which puts the maximum at the head, and the second instance would use a comparator which puts the minimum at the head.

The downside is that add, delete, etc would have to be performed on both structures, but it should satisfy your requirements.

Il-Bhima
  • 10,744
  • 1
  • 47
  • 51
  • 6
    +1. This is a good answer given the stated requirements to peek the root in O(1) and remove it in O(log.n). Note however, that a PriorityQueue doesn't implement all heap operations (e.g. decreaseKey, increaseKey). – dty Sep 16 '10 at 12:36
  • 7
    Note that the no-argument `remove()`, which removes from the head of the queue, is `O(log(n))`, whereas `remove(Object o)`, is `O(n)`. Reference: http://docs.oracle.com/javase/8/docs/api/java/util/PriorityQueue.html – erwaman Jun 01 '16 at 13:48
  • 4
    Downvoted because removal won't be O(log n) anymore. Removing the minimum from one of the queues is O(log n), but removing the identical item, which will be at the end of the other queue, is O(n). – Jim Mischel Aug 12 '16 at 15:13
  • 1
    @JimMischel That was my thought, but then I realized we could still still make the two heap solution work in the following way. When we extract/peek max, we only do it from the max heap, and vice versa for min heap. This, of course, results in two inconsistent heaps. We keep track of the last max extracted and last min extracted, so that when they coincide, the data structure is empty. This would work if we don't need to support insertion and key changes (after extraction has been done), which the OP did not specify as requirements. – flow2k Aug 09 '18 at 16:01
9

Min Heap: PriorityQueue minHeap= new PriorityQueue<>();

Max Heap PriorityQueue maxHeap= new PriorityQueue<>(Comparator.reverseOrder());

Vip
  • 1,448
  • 2
  • 17
  • 20
8

Well you can simply pass the comparator to be used to compare elements. This will become useful even when you want to sort objects based on some attributes. Look at the following examples:

  • Min Heap :

    PriorityQueue<Integer> pq = new PriorityQueue<>((a,b) -> a - b);
    

  • Max Heap :

    PriorityQueue<Integer> pq = new PriorityQueue<>((a,b) -> b - a);
    

  • Min Heap for Objects

    PriorityQueue<MyObject> pq = new PriorityQueue<>((obj1, obj2) -> obj1.getId() - obj2.getId());
    

  • Max Heap for Objects

    PriorityQueue<MyObject> pq = new PriorityQueue<>((obj1, obj2) -> obj2.getId() - obj1.getId());
    

Fahad Israr
  • 1,041
  • 10
  • 9
3

How about com.aliasi.util.MinMaxHeap? This is part of LingPipe; unfortunately, the licensing may be a problem.

See this related paper.

Doesn't implement decreaseKey or increaseKey, though.

Henry Ecker
  • 34,399
  • 18
  • 41
  • 57
NamshubWriter
  • 23,549
  • 2
  • 41
  • 59
0

The java.util.TreeSet class.

dfa
  • 114,442
  • 31
  • 189
  • 228
Nat
  • 9,820
  • 3
  • 31
  • 33
  • 2
    I need support for duplicate values... Set is "a bit" problematic in this aspect. – Yuval Adam Jul 08 '09 at 14:10
  • 3
    -1 TreeSet implements most operations in O(log n) - the requirement was for peeking at the minimum and maximum in O(1). – Avi Jul 08 '09 at 14:12
  • TreeSet also doesn't have a decreaseKey or increaseKey operation, which is one of the definitive operations of a heap. See http://en.wikipedia.org/wiki/Heap_(data_structure) – NamshubWriter Jul 08 '09 at 14:14
  • 1
    If you need to support duplicate values, you can use TreeBag (http://commons.apache.org/collections/api-release/org/apache/commons/collections/bag/TreeBag.html) from Apache Commons Collections, or TreeMultiset (http://google-collections.googlecode.com/svn/trunk/javadoc/com/google/common/collect/TreeMultiset.html) from Google Collections. If you need to increaseKey or decreaseKey, you can simply remove the element and re-add it. – newacct Jul 08 '09 at 18:35
  • 2
    Or use a TreeMap that maps keys to counts. TreeSet is actually implemented with a TreeMap under the hood. – Nat Jul 08 '09 at 18:38
  • @YuvalAdam duplicate values and stable ordering may be implemented by replacing a key by `[key, stamp]` pair, so comparison is first performed on key, then on stamp. Though, `TreeSet` is not a replacement of max-min heap deque. – Alex Salauyou Jun 29 '16 at 09:30