web-dev-qa-db-fra.com

Comment implémenter un tas médian

À l'instar d'un tas max et d'un tas min, je souhaite implémenter un tas médian pour garder une trace de la médiane d'un ensemble donné d'entiers. L'API devrait avoir les trois fonctions suivantes:

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

Je souhaite utiliser une implémentation de tableau (a) pour implémenter le segment de mémoire où les enfants de l'indice de tableau k sont stockés dans les indices de tableau 2 * k et 2 * k + 1. Par commodité, le tableau commence à remplir des éléments à partir de l'index 1. .__ C’est ce que j’ai jusqu’à présent: 

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.

De même pour l'autre cas. Je ne peux pas trouver un algorithme sur la manière de couler et de nager des éléments. Je pense que cela devrait prendre en compte la proximité du nombre par rapport à la médiane, donc quelque chose comme:

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

Je ne peux pas trouver la solution complète cependant. 

27
Bruce

Vous avez besoin de deux tas: un min-tas et un max-tas. Chaque segment contient environ la moitié des données. Chaque élément du min-tas est supérieur ou égal à la médiane et chaque élément du max-tas est inférieur ou égal à la médiane.

Lorsque le min-tas contient un élément de plus que le max-heap, la médiane se trouve en haut du min-heap. Et lorsque le max-tas contient un élément de plus que le min-heap, la médiane se trouve en haut du max-heap.

Lorsque les deux tas contiennent le même nombre d'éléments, le nombre total d'éléments est pair. Dans ce cas, vous devez choisir selon votre définition de la médiane: a) la moyenne des deux éléments du milieu; b) le plus grand des deux; c) le moindre; d) choisissez au hasard l'un des deux ...

Chaque fois que vous insérez, comparez le nouvel élément avec ceux situés en haut des tas afin de décider où l'insérer. Si le nouvel élément est supérieur à la médiane actuelle, il passe au min-tas. S'il est inférieur à la médiane actuelle, il passe au tas maximal. Ensuite, vous devrez peut-être rééquilibrer. Si les tailles des tas diffèrent de plus d'un élément, extrayez le minimum/maximum du tas avec plus d'éléments et insérez-le dans l'autre tas.

Afin de construire le tas médian pour une liste d'éléments, nous devons d'abord utiliser un algorithme de temps linéaire et trouver la médiane. Une fois la médiane connue, nous pouvons simplement ajouter des éléments au min-tas et au max-tas en fonction de la valeur médiane. Il n’est pas nécessaire d’équilibrer les tas, car la médiane divisera la liste d’éléments en deux parties égales.

Si vous extrayez un élément, vous devrez peut-être compenser le changement de taille en déplaçant un élément d'un tas à un autre. De cette façon, vous vous assurez que les deux tas ont toujours la même taille ou ne diffèrent que d’un seul élément.

104
comocomocomocomo

Voici une implémentation Java de MedianHeap, développée à l'aide de l'explication ci-dessus de comocomo comocomo.

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

Voici mon code basé sur la réponse fournie par 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

Un arbre de recherche binaire (BST) parfaitement équilibré n'est-il pas un tas médian? Il est vrai que même les BST rouge-noir ne sont pas toujours parfaitement équilibrés, mais cela peut être assez proche pour vos besoins. Et la performance log (n) est garantie!

Les arbres AVL sont plus étroitement équilibrés que les BST rouge-noir, ce qui les rapproche encore plus de la réalité.

2
angelatlarge

Voici une implémentation de Scala, suivant l'idée du comocomocomocomo ci-dessus.

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

Une autre façon de le faire sans utiliser max-heap et min-heap serait d'utiliser immédiatement un tas médian.

Dans un max-tas, le parent est plus grand que les enfants. Nous pouvons avoir un nouveau type de tas où le parent est au centre des enfants - l'enfant de gauche est plus petit que le parent et l'enfant de droite est plus grand que le parent. Toutes les entrées paires sont des enfants et toutes les entrées impaires sont des enfants corrects.

Les mêmes opérations de nage et de descente qui peuvent être effectuées dans un tas max, peuvent également être effectuées dans ce tas médian - avec de légères modifications. Dans une opération de natation typique dans un max-tas, l'entrée insérée va jusqu'à ce qu'elle soit plus petite qu'une entrée parent, ici dans un tas médian, elle nagera jusqu'à devenir inférieure à un parent ) ou supérieur à un parent (s’il s’agit d’une entrée paire).

Voici ma mise en œuvre pour ce tas médian. J'ai utilisé un tableau de nombres entiers pour plus de simplicité.

package priorityQueues;

importer edu.princeton.cs.algs4.StdOut;

classe publique 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];


}

// entry nage de sorte que son enfant gauche soit plus petit et que son droit soit plus grand Natation privée privée (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;
    }

}

// si l'enfant de gauche est plus grand ou si l'enfant de droite est plus petit, l'entrée disparaît évier privé privé (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());




}

}

0