web-dev-qa-db-fra.com

Trouver la médiane en cours d'exécution à partir d'un flux d'entiers

Duplicate possible:
algorithme médian roulant en C

Étant donné que les entiers sont lus à partir d'un flux de données. Trouver la médiane des éléments lus jusqu'à présent de manière efficace.

Solution que j'ai lue: Nous pouvons utiliser un segment max sur le côté gauche pour représenter les éléments inférieurs à la médiane effective et un segment minimum sur le côté droit pour représenter les éléments supérieurs à la médiane effective.

Après traitement d’un élément entrant, le nombre d’éléments des tas diffère d’au plus 1 élément. Lorsque les deux tas contiennent le même nombre d'éléments, nous trouvons la moyenne des données racine du tas comme médiane effective. Lorsque les tas ne sont pas équilibrés, nous sélectionnons la médiane effective à partir de la racine du tas contenant plus d'éléments.

Mais comment pourrions-nous construire un tas max et un tas min, c’est-à-dire, comment pourrions-nous connaître la médiane effective ici? Je pense que nous insérerions 1 élément dans max-heap, puis le prochain élément dans min-heap, et ainsi de suite pour tous les éléments. Corrigez-moi si je me trompe ici.

217
Luv

Il existe un certain nombre de solutions différentes pour rechercher une médiane en cours à partir de données en continu. J'en parlerai brièvement à la toute fin de la réponse.

La question concerne les détails d'une solution spécifique (solution max heap/min heap) et explique comment une solution basée sur un tas est expliquée ci-dessous:

Pour les deux premiers éléments, ajoutez un plus petit au maxHeap à gauche et un plus grand au minHeap à droite. Ensuite, traitez les données de flux une par une,

Step 1: Add next item to one of the heaps

   if next item is smaller than maxHeap root add it to maxHeap,
   else add it to minHeap

Step 2: Balance the heaps (after this step heaps will be either balanced or
   one of them will contain 1 more item)

   if number of elements in one of the heaps is greater than the other by
   more than 1, remove the root element from the one containing more elements and
   add to the other one

Ensuite, à tout moment, vous pouvez calculer la médiane comme ceci:

   If the heaps contain equal amount of elements;
     median = (root of maxHeap + root of minHeap)/2
   Else
     median = root of the heap with more elements

Je vais maintenant parler du problème en général, comme promis au début de la réponse. Trouver la médiane courante à partir d'un flux de données est un problème difficile, et trouver une solution exacte avec des contraintes de mémoire efficacement est probablement impossible dans le cas général. D'autre part, si les données ont des caractéristiques que nous pouvons exploiter, nous pouvons développer des solutions spécialisées efficaces. Par exemple, si nous savons que les données sont de type intégral, alors nous pouvons utiliser sorte de comptage , ce qui peut vous donner un algorithme de temps de mémoire constant. La solution basée sur le tas est une solution plus générale car elle peut également être utilisée pour d'autres types de données (doubles). Enfin, si la médiane exacte n’est pas requise et qu’une approximation suffit, vous pouvez simplement essayer d’estimer une fonction de densité de probabilité pour les données et d’estimer la médiane à l’aide de cette fonction.

374
Hakan Serce

Si vous ne pouvez pas conserver tous les éléments en mémoire en même temps, ce problème devient beaucoup plus difficile. La solution de tas nécessite que vous gardiez tous les éléments en mémoire en même temps. Ce n'est pas possible dans la plupart des applications réelles de ce problème.

Lorsque vous voyez des chiffres, gardez une trace du nombre count du nombre de fois que vous voyez chaque nombre entier. En supposant des entiers sur 4 octets, cela correspond à 2 ^ 32 compartiments, ou au plus à 2 ^ 33 entiers (clé et nombre pour chaque int), ce qui correspond à 2 ^ 35 octets ou 32 Go. Ce sera probablement beaucoup moins que cela parce que vous n'avez pas besoin de stocker la clé ou de compter pour les entrées qui sont 0 (c'est-à-dire comme un defaultdict en python). Cela prend un temps constant pour insérer chaque nouvel entier.

Puis, à n'importe quel moment, pour trouver la médiane, utilisez simplement les comptes pour déterminer quel entier est l'élément central. Cela prend du temps constant (bien qu’une grande constante, mais néanmoins constante).

50
Andrew C

Si la variance de l'entrée est distribuée de manière statistique (par exemple normale, log-normale, etc.), l'échantillonnage de réservoir est un moyen raisonnable d'estimer les centiles/les médianes à partir d'un flux de nombres arbitrairement long.

int n = 0;  // Running count of elements observed so far  
#define SIZE 10000
int reservoir[SIZE];  

while(streamHasData())
{
  int x = readNumberFromStream();

  if (n < SIZE)
  {
       reservoir[n++] = x;
  }         
  else 
  {
      int p = random(++n); // Choose a random number 0 >= p < n
      if (p < SIZE)
      {
           reservoir[p] = x;
      }
  }
}

"réservoir" est alors un échantillon courant, uniforme (moyen), de toutes les entrées - quelle que soit leur taille. Trouver la médiane (ou n'importe quel centile) est alors une affaire simple de trier le réservoir et d’interroger le point intéressant.

Étant donné que le réservoir a une taille fixe, le tri peut être considéré comme étant effectivement O(1) - et cette méthode fonctionne avec une consommation de temps et une mémoire constantes.

46

Le moyen le plus efficace de calculer le centile d'un flux que j'ai trouvé est l'algorithme P²: Raj Jain, Imrich Chlamtac: L'algorithme P² pour le calcul dynamique de quantiiles et d'histogrammes sans stockage d'observations. Commun. ACM 28 (10 ): 1076-1085 (1985)

L'algorithme est simple à implémenter et fonctionne extrêmement bien. Cependant, c'est une estimation, alors gardez cela à l'esprit. De l'abstrait:

Un algorithme heuristique est proposé pour le calcul dynamique de la médiane et d’autres quantiles. Les estimations sont produites dynamiquement à mesure que les observations sont générées. Les observations ne sont pas stockées; par conséquent, l'algorithme a une très petite quantité de mémoire nécessaire, quel que soit le nombre d'observations. Cela le rend idéal pour la mise en œuvre dans une puce quantile qui peut être utilisée dans les contrôleurs et les enregistreurs industriels. L'algorithme est étendu au traçage d'histogramme. La précision de l'algorithme est analysée.

28
Hellblazer

Ce problème a une solution exacte ne nécessitant que les derniers éléments vus n à conserver en mémoire. Il est rapide et évolue bien.

Un skiplist indexable prend en charge l’insertion, le retrait et la recherche indexée d’éléments arbitraires par O (ln n) tout en maintenant l’ordre trié. Lorsqu'elle est associée à une file d'attente FIFO qui suit la nième entrée la plus ancienne, la solution est simple:

class RunningMedian:
    'Fast running median with O(lg n) updates where n is the window size'

    def __init__(self, n, iterable):
        self.it = iter(iterable)
        self.queue = deque(islice(self.it, n))
        self.skiplist = IndexableSkiplist(n)
        for elem in self.queue:
            self.skiplist.insert(elem)

    def __iter__(self):
        queue = self.queue
        skiplist = self.skiplist
        midpoint = len(queue) // 2
        yield skiplist[midpoint]
        for newelem in self.it:
            oldelem = queue.popleft()
            skiplist.remove(oldelem)
            queue.append(newelem)
            skiplist.insert(newelem)
            yield skiplist[midpoint]

Voici des liens pour compléter le code de travail (une version de classe facile à comprendre et une version de générateur optimisée avec le code de skiplist indexable en ligne):

27

Une façon intuitive de penser à cela est que si vous aviez un arbre de recherche binaire parfaitement équilibré, la racine serait l'élément médian, puisqu'il y aurait le même nombre d'éléments plus petits et plus grands. Maintenant, si l'arbre n'est pas complet, ce ne sera pas tout à fait le cas puisqu'il manquera des éléments du dernier niveau.

Donc, ce que nous pouvons faire à la place est d’avoir la médiane et deux arbres binaires équilibrés, un pour les éléments inférieurs à la médiane et un pour les éléments supérieurs à la médiane. Les deux arbres doivent être conservés à la même taille.

Lorsque nous obtenons un nouvel entier du flux de données, nous le comparons à la médiane. Si elle est supérieure à la médiane, nous l'ajoutons à l'arbre de droite. Si les deux tailles d'arbre diffèrent de plus de 1, supprimons l'élément min de l'arbre de droite, en faisons la nouvelle médiane et plaçons l'ancienne médiane dans l'arbre de gauche. De même pour les plus petits.

16

Efficace est un mot qui dépend du contexte. La solution à ce problème dépend du nombre de requêtes effectuées par rapport au nombre d'insertions. Supposons que vous insérez N nombres et K fois vers la fin de la médiane. La complexité de l'algorithme basé sur le tas serait O (N log N + K).

Considérez l'alternative suivante. Plunk les nombres dans un tableau, et pour chaque requête, exécutez l'algorithme de sélection linéaire (en utilisant le pivot quicksort, par exemple). Vous avez maintenant un algorithme avec le temps d'exécution O (K N).

Or, si K est suffisamment petit (requêtes peu fréquentes), ce dernier algorithme est en réalité plus efficace et inversement.

7
Peteris