web-dev-qa-db-fra.com

C ++ calculer efficacement une médiane en cours d'exécution

Ceux d'entre vous qui ont lu mes questions précédentes connaissent mon travail pour comprendre et implémenter quicksort et quickselect, ainsi que certains autres algorithmes de base.

Quickselect est utilisé pour calculer le kème plus petit élément dans une liste non triée, et ce concept peut également être utilisé pour trouver la médiane dans une liste non triée.

Cette fois, j'ai besoin d'aide pour concevoir une technique efficace pour calculer la médiane courante, car la sélection rapide n'est pas un bon choix car elle doit recalculer chaque fois que la liste change. Parce que quickselect doit redémarrer à chaque fois, il ne peut pas tirer parti des calculs précédents, donc je cherche un algorithme différent qui est similaire (peut-être) mais qui est plus efficace dans le domaine de l'exécution des médianes.

25
Edge

La médiane de streaming est calculée en utilisant deux tas. Tous les nombres inférieurs ou égaux à la médiane actuelle se trouvent dans le tas de gauche, qui est organisé de manière à ce que le nombre maximum soit à la racine du tas. Tous les nombres supérieurs ou égaux à la médiane actuelle se trouvent dans le tas de droite, qui est organisé de manière à ce que le nombre minimum soit à la racine du tas. Notez que des nombres égaux à la médiane actuelle peuvent être dans l'un ou l'autre tas. Le nombre de nombres dans les deux tas ne diffère jamais de plus de 1.

Lorsque le processus commence, les deux tas sont initialement vides. Le premier nombre de la séquence d'entrée est ajouté à l'un des tas, peu importe lequel, et renvoyé comme première médiane de streaming. Le deuxième nombre de la séquence d'entrée est ensuite ajouté à l'autre tas, si la racine du tas droit est inférieure à la racine du tas gauche, les deux tas sont échangés et la moyenne des deux nombres est renvoyée en tant que deuxième streaming médian.

Ensuite, l'algorithme principal commence. Chaque numéro suivant de la séquence d'entrée est comparé à la médiane actuelle et ajouté au tas de gauche s'il est inférieur à la médiane actuelle ou au tas de droite s'il est supérieur à la médiane actuelle; si le nombre d'entrée est égal à la médiane actuelle, il est ajouté à celui qui a le plus petit nombre ou à l'un ou l'autre tas arbitrairement s'ils ont le même nombre. Si cela fait que le nombre des deux tas diffère de plus de 1, la racine du tas le plus grand est supprimée et insérée dans le plus petit tas. Ensuite, la médiane actuelle est calculée comme la racine du plus grand tas, si elles diffèrent en nombre, ou la moyenne des racines des deux tas, si elles sont de la même taille.

Code dans Scheme et Python est disponible sur mon blog .

44
user448810

L'estimation médiane de Jeff McClintock. Nécessite de ne conserver que deux valeurs. Cet exemple parcourt un tableau de valeurs échantillonnées (consommation CPU). Semble converger relativement rapidement (environ 100 échantillons) vers une estimation de la médiane. L'idée est à chaque itération les pouces médians vers le signal d'entrée à un taux constant. Le taux dépend de la magnitude que vous estimez être la médiane. J'utilise la moyenne comme estimation de l'ampleur de la médiane, pour déterminer la taille de chaque incrément de la médiane. Si vous avez besoin d'une médiane précise à environ 1%, utilisez une taille de pas de 0,01 * la moyenne.

float median = 0.0f;
float average = 0.0f;

// for each sample
{
    average += ( sample - average ) * 0.1f; // rough running average.
    median += _copysign( average * 0.01, sample - median );
}

enter image description here

16
Jeff McClintock

Une solution consisterait à maintenir un arbre statistique d'ordre , en insérant tour à tour chaque élément de la séquence, puis à calculer la médiane des éléments de l'arbre.

Cela prendrait O (lg n ) temps par insertion et O (lg n ) temps par médiane, pour un total de O ( n lg n ) temps, plus O ( n ) espace.

6
Fred Foo

Voici une structure arborescente équilibrée C++ qui offre la possibilité d'interroger par index dans la liste triée. Puisqu'il maintient toutes les valeurs dans l'ordre trié, ce n'est pas aussi efficace que l'approche en deux tas, mais il offre une certaine flexibilité supplémentaire. Par exemple, cela pourrait également vous donner un quartile courant.

template <typename T>
class Node
{
public:
    T key;
    Node* left;
    Node* right;
    size_t size;

    Node(T k) : key(k)
    {
        isolate();
    }

    ~Node()
    {
        delete(left);
        delete(right);
    }

    void isolate()
    {
        left = NULL;
        right = NULL;
        size = 1;
    }

    void recount()
    {
        size = 1 + (left ? left->size : 0) + (right ? right->size : 0);
    }

    Node<T>* rotateLeft()
    {
        Node<T>* c = right;
        Node<T>* gc = right->left;
        right = gc;
        c->left = this;
        recount();
        c->recount();
        return c;
    }

    Node<T>* rotateRight()
    {
        Node<T>* c = left;
        Node<T>* gc = left->right;
        left = gc;
        c->right = this;
        recount();
        c->recount();
        return c;
    }

    Node<T>* balance()
    {
        size_t lcount = left ? left->size : 0;
        size_t rcount = right ? right->size : 0;
        if((lcount + 1) * 2 < (rcount + 1))
        {
            size_t lcount2 = right->left ? right->left->size : 0;
            size_t rcount2 = right->right ? right->right->size : 0;
            if(lcount2 > rcount2)
                right = right->rotateRight();
            return rotateLeft();
        }
        else if((rcount + 1) * 2 <= (lcount + 1))
        {
            size_t lcount2 = left->left ? left->left->size : 0;
            size_t rcount2 = left->right ? left->right->size : 0;
            if(lcount2 < rcount2)
                left = left->rotateLeft();
            return rotateRight();
        }
        else
        {
            recount();
            return this;
        }
    }

    Node<T>* insert(Node<T>* newNode)
    {
        if(newNode->key < key)
        {
            if(left)
                left = left->insert(newNode);
            else
                left = newNode;
        }
        else
        {
            if(right)
                right = right->insert(newNode);
            else
                right = newNode;
        }
        return balance();
    }

    Node<T>* get(size_t index)
    {
        size_t lcount = left ? left->size : 0;
        if(index < lcount)
            return left->get(index);
        else if(index > lcount)
            return right ? right->get(index - lcount - 1) : NULL;
        else
            return this;
    }

    Node<T>* find(T k, size_t start, size_t* outIndex)
    {
        if(k < key)
            return left ? left->find(k, start, outIndex) : NULL;
        else if(k > key)
            return right ? right->find(k, left ? start + left->size + 1 : start + 1, outIndex) : NULL;
        else
        {
            if(outIndex)
                *outIndex = start + (left ? left->size : 0);
            return this;
        }
    }

    Node<T>* remove_by_index(size_t index, Node<T>** outNode)
    {
        size_t lcount = left ? left->size : 0;
        if(index < lcount)
            left = left->remove_by_index(index, outNode);
        else if(index > lcount)
            right = right->remove_by_index(index - lcount - 1, outNode);
        else
        {
            *outNode = this;
            size_t rcount = right ? right->size : 0;
            if(lcount < rcount)
                return left ? right->insert(left) : right;
            else
                return right ? left->insert(right) : left;
        }
        return balance();
    }

    Node<T>* remove_by_value(T k, Node<T>** outNode)
    {
        if(k < key)
        {
            if(!left)
                throw "not found";
            left = left->remove_by_value(k, outNode);
        }
        else if(k > key)
        {
            if(!right)
                throw "not found";
            right = right->remove_by_value(k, outNode);
        }
        else
        {
            *outNode = this;
            size_t lcount = left ? left->size : 0;
            size_t rcount = right ? right->size : 0;
            if(lcount < rcount)
                return left ? right->insert(left) : right;
            else
                return right ? left->insert(right) : left;
        }
        return balance();
    }
};

template <typename T>
class MyReasonablyEfficientRunningSortedIndexedCollection
{
private:
    Node<T>* root;
    Node<T>* spare;

public:
    MyReasonablyEfficientRunningSortedIndexedCollection() : root(NULL), spare(NULL)
    {
    }

    ~MyReasonablyEfficientRunningSortedIndexedCollection()
    {
        delete(root);
        delete(spare);
    }

    void insert(T key)
    {
        if(spare)
            spare->key = key;
        else
            spare = new Node<T>(key);
        if(root)
            root = root->insert(spare);
        else
            root = spare;
        spare = NULL;
    }

    void drop_by_index(size_t index)
    {
        if(!root || index >= root->size)
            throw "out of range";
        delete(spare);
        root = root->remove_by_index(index, &spare);
        spare->isolate();
    }

    void drop_by_value(T key)
    {
        if(!root)
            throw "out of range";
        delete(spare);
        root = root->remove_by_value(key, &spare);
        spare->isolate();
    }

    T get(size_t index)
    {
        if(!root || index >= root->size)
            throw "out of range";
        return root->get(index)->key;
    }

    size_t find(T key)
    {
        size_t outIndex;
        Node<T>* node = root ? root->find(key, 0, &outIndex) : NULL;
        if(node)
            return outIndex;
        else
            throw "not found";
    }

    size_t size()
    {
        return root ? root->size : 0;
    }
};
1
Mike Gashler

Algorithme de médiane de fenêtre déroulante:

médiane est un tableau trié où vous en tirez la valeur intermédiaire.

l'implémentation simple de roulement est avec une file d'attente (dqueue) et un sorted_array (n'importe quelle implémentation, arbre binaire, skiparray).

d_queue est un tableau où vous pouvez pousser vers la fin et déplacer (pop) depuis l'avant du tableau.

sorted_array est un tableau où vous insérez par ordre à la position trouvée à l'aide de la recherche binaire.

J'ai utilisé une file d'attente (tableau premier entré, premier sorti) pour suivre l'ordre des valeurs ajoutées afin de savoir quels éléments supprimer du tableau médian, lorsque la file d'attente est plus longue que la taille souhaitée. pour supprimer les éléments par date-heure ou par un index en cours d'exécution, il est possible d'ajouter une autre file d'attente et de vérifier que le premier élément est trop ancien, et de décider s'il faut supprimer la première valeur des deux files d'attente.

Pour calculer efficacement une médiane, j'utilise une technique de tableau trié. c'est lorsque vous insérez de nouveaux éléments dans son emplacement trié, le tableau est donc toujours trié.

  1. L'insert:

    • Insérer à l'endroit ordonné dans le tableau trié,
    • et Poussez une valeur dans une file d'attente.
  2. La suppression:

    • Si le premier élément d_queue est hors de la fenêtre, ou si dans une autre file d'attente que vous pouvez avoir avec des index, l'index est trop ancien, alors:
      • supprimer le premier élément de d_queue (s),
      • et la recherche binaire dans le tableau trié et le supprimer.
  3. Pour avoir la médiane:

    • Utilisez la ou les valeurs au milieu du tableau sorted.
    • Si sorted_array est égal, utilisez l'élément au milieu.
    • Si sorted_array est impair, utilisez en moyenne deux éléments au milieu.
1
Shimon Doodkin
#include<cstdio>
#include<iostream>
#include<queue>
#include <vector>         
#include <functional>

typedef priority_queue<unsigned int> type_H_low;
typedef priority_queue<unsigned int, std::vector<unsigned int>, std::greater<unsigned int> > type_H_high;

size_t signum(int left, int right) {
    if (left == right){
        return 0;
    }
    return (left < right)?-1:1;
}

void get_median( unsigned int x_i, unsigned int &m, type_H_low *l, type_H_high *r) {

    switch (signum( l->size(), r->size() )) {
    case 1: // There are more elements in left (max) heap
        if (x_i < m) {
            r->Push(l->top());
            l->pop();
            l->Push(x_i);
        } else {
            r->Push(x_i);
        }
        break;

    case 0: // The left and right heaps contain same number of elements
        if (x_i < m){
            l->Push(x_i);
        } else {
            r->Push(x_i);
        }
        break;

    case -1: // There are more elements in right (min) heap
        if (x_i < m){
            l->Push(x_i);
        } else {
            l->Push(r->top());
            r->pop();
            r->Push(x_i);
        }
        break;
    }

    if (l->size() == r->size()){
        m = l->top();
    } else if (l->size() > r->size()){
        m = l->top();
    } else {
        m = r->top();
    }

    return;
}

void print_median(vector<unsigned int> v) {
    unsigned int median = 0;
    long int sum = 0;
    type_H_low  H_low;
    type_H_high H_high;

    for (vector<unsigned int>::iterator x_i = v.begin(); x_i != v.end(); x_i++) {
        get_median(*x_i, median, &H_low, &H_high);
        std::cout << median << std::endl;
    }
}
0