it-swarm.com.de

So implementieren Sie einen Median-Heap

Wie ein Max-Heap und Min-Heap möchte ich einen Median-Heap implementieren, um den Median einer bestimmten Menge von Ganzzahlen zu verfolgen. Die API sollte die folgenden drei Funktionen haben:

insert(int)  // should take O(logN)
int median() // will be the topmost element of the heap. O(1)
int delmedian() // should take O(logN)

Ich möchte eine Array (a) -Implementierung verwenden, um den Heap zu implementieren, in dem die Kinder des Array-Index k in den Array-Indizes 2 * k und 2 * k + 1 gespeichert sind. Das Array füllt die Elemente aus Index 1 aus. Dies ist, was ich bis jetzt habe: Der Median-Haufen wird zwei Ganzzahlen haben, um die Anzahl der insoweit eingefügten Ganzzahlen zu verfolgen, die> aktueller Median (gcm) und <aktueller Median (lcm) sind. 

if abs(gcm-lcm) >= 2 and gcm > lcm we need to swap a[1] with one of its children. 
The child chosen should be greater than a[1]. If both are greater, 
choose the smaller of two.

Ähnlich für den anderen Fall. Ich kann mir keinen Algorithmus für das Senken und Schwimmen von Elementen ausdenken. Ich denke, es sollte berücksichtigen, wie nahe die Zahl an dem Median ist, also etwa:

private void swim(int k) {
    while (k > 1 && absless(k, k/2)) {   
        exch(k, k/2);
        k = k/2;
    }
}

Ich kann jedoch nicht die gesamte Lösung finden. 

27
Bruce

Sie benötigen zwei Haufen: einen Min-Heap und einen Max-Heap. Jeder Heap enthält ungefähr die Hälfte der Daten. Jedes Element im Min-Heap ist größer oder gleich dem Median, und jedes Element im Max-Heap ist kleiner oder gleich dem Median.

Wenn der Min-Heap ein Element mehr enthält als der Max-Heap, befindet sich der Median oben auf dem Min-Heap. Und wenn der Max-Heap ein Element mehr enthält als der Min-Heap, befindet sich der Median oben auf dem Max-Heap.

Wenn beide Heaps die gleiche Anzahl von Elementen enthalten, ist die Gesamtzahl der Elemente gerade. In diesem Fall müssen Sie entsprechend Ihrer Definition des Medians wählen: a) den Mittelwert der beiden mittleren Elemente; b) der größere von beiden; c) der kleinere; d) Wählen Sie zufällig eine der beiden ...

Vergleichen Sie bei jedem Einfügen das neue Element mit den Elementen am oberen Rand der Haufen, um zu entscheiden, wo es eingefügt werden soll. Wenn das neue Element größer als der aktuelle Median ist, geht es zum Min-Heap. Wenn es unter dem aktuellen Median liegt, geht es zum Max-Heap. Dann müssen Sie möglicherweise einen Ausgleich schaffen. Wenn sich die Größe der Heaps um mehr als ein Element unterscheidet, extrahieren Sie die Min/Max-Werte mit weiteren Elementen aus dem Heap und fügen Sie sie in den anderen Heap ein.

Um den Medianheap für eine Liste von Elementen zu erstellen, sollten wir zuerst einen linearen Zeitalgorithmus verwenden und den Median finden. Sobald der Median bekannt ist, können wir dem Min-Heap und Max-Heap basierend auf dem Medianwert einfach Elemente hinzufügen. Das Ausgleichen der Haufen ist nicht erforderlich, da der Median die Eingabeliste der Elemente in gleiche Hälften aufteilt.

Wenn Sie ein Element extrahieren, müssen Sie möglicherweise die Größenänderung kompensieren, indem Sie ein Element von einem Heap zu einem anderen verschieben. Auf diese Weise stellen Sie sicher, dass beide Heaps immer gleich groß sind oder sich nur um ein Element unterscheiden.

104

Hier ist eine Java-Implementierung eines MedianHeap, die mit Hilfe der obigen Erklärung von Comocomo Comocomo entwickelt wurde.

import Java.util.Arrays;
import Java.util.Comparator;
import Java.util.PriorityQueue;
import Java.util.Scanner;

/**
 *
 * @author BatmanLost
 */
public class MedianHeap {

    //stores all the numbers less than the current median in a maxheap, i.e median is the maximum, at the root
    private PriorityQueue<Integer> maxheap;
    //stores all the numbers greater than the current median in a minheap, i.e median is the minimum, at the root
    private PriorityQueue<Integer> minheap;

    //comparators for PriorityQueue
    private static final maxHeapComparator myMaxHeapComparator = new maxHeapComparator();
    private static final minHeapComparator myMinHeapComparator = new minHeapComparator();

    /**
     * Comparator for the minHeap, smallest number has the highest priority, natural ordering
     */
    private static class minHeapComparator implements Comparator<Integer>{
        @Override
        public int compare(Integer i, Integer j) {
            return i>j ? 1 : i==j ? 0 : -1 ;
        }
    }

    /**
     * Comparator for the maxHeap, largest number has the highest priority
     */
    private static  class maxHeapComparator implements Comparator<Integer>{
        // opposite to minHeapComparator, invert the return values
        @Override
        public int compare(Integer i, Integer j) {
            return i>j ? -1 : i==j ? 0 : 1 ;
        }
    }

    /**
     * Constructor for a MedianHeap, to dynamically generate median.
     */
    public MedianHeap(){
        // initialize maxheap and minheap with appropriate comparators
        maxheap = new PriorityQueue<Integer>(11,myMaxHeapComparator);
        minheap = new PriorityQueue<Integer>(11,myMinHeapComparator);
    }

    /**
     * Returns empty if no median i.e, no input
     * @return
     */
    private boolean isEmpty(){
        return maxheap.size() == 0 && minheap.size() == 0 ;
    }

    /**
     * Inserts into MedianHeap to update the median accordingly
     * @param n
     */
    public void insert(int n){
        // initialize if empty
        if(isEmpty()){ minheap.add(n);}
        else{
            //add to the appropriate heap
            // if n is less than or equal to current median, add to maxheap
            if(Double.compare(n, median()) <= 0){maxheap.add(n);}
            // if n is greater than current median, add to min heap
            else{minheap.add(n);}
        }
        // fix the chaos, if any imbalance occurs in the heap sizes
        //i.e, absolute difference of sizes is greater than one.
        fixChaos();
    }

    /**
     * Re-balances the heap sizes
     */
    private void fixChaos(){
        //if sizes of heaps differ by 2, then it's a chaos, since median must be the middle element
        if( Math.abs( maxheap.size() - minheap.size()) > 1){
            //check which one is the culprit and take action by kicking out the root from culprit into victim
            if(maxheap.size() > minheap.size()){
                minheap.add(maxheap.poll());
            }
            else{ maxheap.add(minheap.poll());}
        }
    }
    /**
     * returns the median of the numbers encountered so far
     * @return
     */
    public double median(){
        //if total size(no. of elements entered) is even, then median iss the average of the 2 middle elements
        //i.e, average of the root's of the heaps.
        if( maxheap.size() == minheap.size()) {
            return ((double)maxheap.peek() + (double)minheap.peek())/2 ;
        }
        //else median is middle element, i.e, root of the heap with one element more
        else if (maxheap.size() > minheap.size()){ return (double)maxheap.peek();}
        else{ return (double)minheap.peek();}

    }
    /**
     * String representation of the numbers and median
     * @return 
     */
    public String toString(){
        StringBuilder sb = new StringBuilder();
        sb.append("\n Median for the numbers : " );
        for(int i: maxheap){sb.append(" "+i); }
        for(int i: minheap){sb.append(" "+i); }
        sb.append(" is " + median()+"\n");
        return sb.toString();
    }

    /**
     * Adds all the array elements and returns the median.
     * @param array
     * @return
     */
    public double addArray(int[] array){
        for(int i=0; i<array.length ;i++){
            insert(array[i]);
        }
        return median();
    }

    /**
     * Just a test
     * @param N
     */
    public void test(int N){
        int[] array = InputGenerator.randomArray(N);
        System.out.println("Input array: \n"+Arrays.toString(array));
        addArray(array);
        System.out.println("Computed Median is :" + median());
        Arrays.sort(array);
        System.out.println("Sorted array: \n"+Arrays.toString(array));
        if(N%2==0){ System.out.println("Calculated Median is :" + (array[N/2] + array[(N/2)-1])/2.0);}
        else{System.out.println("Calculated Median is :" + array[N/2] +"\n");}
    }

    /**
     * Another testing utility
     */
    public void printInternal(){
        System.out.println("Less than median, max heap:" + maxheap);
        System.out.println("Greater than median, min heap:" + minheap);
    }

    //Inner class to generate input for basic testing
    private static class InputGenerator {

        public static int[] orderedArray(int N){
            int[] array = new int[N];
            for(int i=0; i<N; i++){
                array[i] = i;
            }
            return array;
        }

        public static int[] randomArray(int N){
            int[] array = new int[N];
            for(int i=0; i<N; i++){
                array[i] = (int)(Math.random()*N*N);
            }
            return array;
        }

        public static int readInt(String s){
            System.out.println(s);
            Scanner sc = new Scanner(System.in);
            return sc.nextInt();
        }
    }

    public static void main(String[] args){
        System.out.println("You got to stop the program MANUALLY!!");        
        while(true){
            MedianHeap testObj = new MedianHeap();
            testObj.test(InputGenerator.readInt("Enter size of the array:"));
            System.out.println(testObj);
        }
    }
}
7
Charan

Hier mein Code basierend auf der Antwort von comocomocomocomo:

import Java.util.PriorityQueue;

public class Median {
private  PriorityQueue<Integer> minHeap = 
    new PriorityQueue<Integer>();
private  PriorityQueue<Integer> maxHeap = 
    new PriorityQueue<Integer>((o1,o2)-> o2-o1);

public float median() {
    int minSize = minHeap.size();
    int maxSize = maxHeap.size();
    if (minSize == 0 && maxSize == 0) {
        return 0;
    }
    if (minSize > maxSize) {
        return minHeap.peek();
    }if (minSize < maxSize) {
        return maxHeap.peek();
    }
    return (minHeap.peek()+maxHeap.peek())/2F;
}

public void insert(int element) {
    float median = median();
    if (element > median) {
        minHeap.offer(element);
    } else {
        maxHeap.offer(element);
    }
    balanceHeap();
}

private void balanceHeap() {
    int minSize = minHeap.size();
    int maxSize = maxHeap.size();
    int tmp = 0;
    if (minSize > maxSize + 1) {
        tmp = minHeap.poll();
        maxHeap.offer(tmp);
    }
    if (maxSize > minSize + 1) {
        tmp = maxHeap.poll();
        minHeap.offer(tmp);
    }
  }
}
3
Enrico Giurin

Ist ein perfekt ausbalancierter Binärsuchbaum (BST) kein mittlerer Haufen? Es ist wahr, dass selbst rot-schwarze BSTs nicht immer perfekt ausbalanciert sind, aber es könnte für Ihre Zwecke nahe genug sein. Und Log (n) Performance ist garantiert!

AVL-Bäume sind stärker ausbalanciert als rot-schwarze BSTs, sodass sie einem echten Mittelhaufen noch näher kommen.

2
angelatlarge

Hier ist eine Scala-Implementierung, die der oben genannten Idee des Comocomocomocomo folgt.

class MedianHeap(val capacity:Int) {
    private val minHeap = new PriorityQueue[Int](capacity / 2)
    private val maxHeap = new PriorityQueue[Int](capacity / 2, new Comparator[Int] {
      override def compare(o1: Int, o2: Int): Int = Integer.compare(o2, o1)
    })

    def add(x: Int): Unit = {
      if (x > median) {
        minHeap.add(x)
      } else {
        maxHeap.add(x)
      }

      // Re-balance the heaps.
      if (minHeap.size - maxHeap.size > 1) {
        maxHeap.add(minHeap.poll())
      }
      if (maxHeap.size - minHeap.size > 1) {
        minHeap.add(maxHeap.poll)
      }
    }

    def median: Double = {
      if (minHeap.isEmpty && maxHeap.isEmpty)
        return Int.MinValue
      if (minHeap.size == maxHeap.size) {
        return (minHeap.peek+ maxHeap.peek) / 2.0
      }
      if (minHeap.size > maxHeap.size) {
        return minHeap.peek()
      }
      maxHeap.peek
    }
  }
1
Duong Nguyen

Eine andere Möglichkeit, einen Max-Heap und einen Min-Heap zu verwenden, wäre die Verwendung eines Median-Heaps.

In einem Max-Heap ist das übergeordnete Element größer als das untergeordnete Element. Wir können einen neuen Heap-Typ haben, bei dem sich das übergeordnete Element in der Mitte der untergeordneten Elemente befindet. Das linke untergeordnete Element ist kleiner als das übergeordnete und das rechte untergeordnete Element ist größer als das übergeordnete Element. Alle geradzahligen Einträge sind untergeordnete Elemente, und alle ungeraden Einträge sind rechte untergeordnete Elemente.

Die gleichen Schwimm- und Sinkoperationen, die in einem Max-Heap ausgeführt werden können, können auch in diesem Median-Heap ausgeführt werden - mit geringfügigen Änderungen. Bei einer typischen Schwimmoperation in einem Max-Heap schwimmt der eingefügte Eintrag, bis er kleiner als ein übergeordneter Eintrag ist. Hier in einem mittleren Haufen schwimmt er nach oben, bis er kleiner ist als ein Elternteil (wenn es sich um einen ungeraden Eintrag handelt.) ) oder größer als ein Elternteil (wenn es sich um einen geraden Eintrag handelt).

Hier ist meine Implementierung für diesen Median-Heap. Ich habe zur Vereinfachung eine Reihe von ganzen Zahlen verwendet.

package priorityQueues;

import edu.princeton.cs.algs4.StdOut;

öffentliche Klasse MedianInsertDelete {

private Integer[] a;
private int N;

public MedianInsertDelete(int capacity){

    // accounts for '0' not being used
    this.a = new Integer[capacity+1]; 
    this.N = 0;
}

public void insert(int k){

    a[++N] = k;
    swim(N);
}

public int delMedian(){

    int median = findMedian();
    exch(1, N--);
    sink(1);
    a[N+1] = null;
    return median;

}

public int findMedian(){

    return a[1];


}

// Eintrag schwimmt so hoch, dass sein linkes Kind kleiner und rechts größer ist privates Leerenschwimmen (int k) {

    while(even(k) && k>1 && less(k/2,k)){

        exch(k, k/2);

        if ((N > k) && less (k+1, k/2)) exch(k+1, k/2);
        k = k/2;
    }

    while(!even(k) && (k>1 && !less(k/2,k))){

        exch(k, k/2);
        if (!less (k-1, k/2)) exch(k-1, k/2);
        k = k/2;
    }

}

// Wenn das linke Kind größer oder das rechte Kind kleiner ist, sinkt der Eintrag private leere Senke (int k) {

    while(2*k <= N){
        int j = 2*k;
        if (j < N && less (j, k)) j++;
        if (less(k,j)) break;
        exch(k, j);
        k = j;
    }

}

private boolean even(int i){

    if ((i%2) == 0) return true;
    else return false;
}

private void exch(int i, int j){

    int temp = a[i];
    a[i] = a[j];
    a[j] = temp;
}

private boolean less(int i, int j){

    if (a[i] <= a[j]) return true;
    else return false;
}


public static void main(String[] args) {

    MedianInsertDelete medianInsertDelete = new MedianInsertDelete(10);

    for(int i = 1; i <=10; i++){

        medianInsertDelete.insert(i);
    }

    StdOut.println("The median is: " + medianInsertDelete.findMedian());

    medianInsertDelete.delMedian();


    StdOut.println("Original median deleted. The new median is " + medianInsertDelete.findMedian());




}

}