web-dev-qa-db-fra.com

Quelle est la bonne approche lors de l'utilisation du conteneur STL pour le calcul médian?

Disons que je dois récupérer la médiane d'une séquence de 1000000 valeurs numériques aléatoires.

Si vous utilisez quelque chose maisstd::list, Je n'ai aucun moyen (intégré) de trier la séquence pour le calcul médian.

Si vous utilisez std::list, Je ne peux pas accéder aléatoirement aux valeurs pour récupérer le milieu (médiane) de la séquence triée.

Est-il préférable de mettre en œuvre le tri moi-même et d'aller avec par exemple std::vector, ou est-il préférable d'utiliser std::list et utilise std::list::iterator pour for-loop-walk jusqu'à la valeur médiane? Ce dernier semble moins lourd, mais se sent aussi plus laid.

Ou existe-t-il des alternatives plus nombreuses et meilleures pour moi?

43
sharkin

Tout conteneur à accès aléatoire (comme std::vector) peut être trié avec la norme std::sort algorithme, disponible dans le <algorithm> entête.

Pour trouver la médiane, il serait plus rapide d'utiliser std::nth_element; cela fait assez de tri pour mettre un élément choisi dans la bonne position, mais ne trie pas complètement le conteneur. Vous pouvez donc trouver la médiane comme ceci:

int median(vector<int> &v)
{
    size_t n = v.size() / 2;
    nth_element(v.begin(), v.begin()+n, v.end());
    return v[n];
}
97
Mike Seymour

La médiane est plus complexe que la réponse de Mike Seymour. La médiane diffère selon qu'il y a un nombre pair ou impair d'articles dans l'échantillon. S'il y a un nombre pair d'éléments, la médiane est la moyenne des deux éléments du milieu. Cela signifie que la médiane d'une liste d'entiers peut être une fraction. Enfin, la médiane d'une liste vide n'est pas définie. Voici le code qui passe mes cas de test de base:

///Represents the exception for taking the median of an empty list
class median_of_empty_list_exception:public std::exception{
  virtual const char* what() const throw() {
    return "Attempt to take the median of an empty list of numbers.  "
      "The median of an empty list is undefined.";
  }
};

///Return the median of a sequence of numbers defined by the random
///access iterators begin and end.  The sequence must not be empty
///(median is undefined for an empty set).
///
///The numbers must be convertible to double.
template<class RandAccessIter>
double median(RandAccessIter begin, RandAccessIter end) 
  throw(median_of_empty_list_exception){
  if(begin == end){ throw median_of_empty_list_exception(); }
  std::size_t size = end - begin;
  std::size_t middleIdx = size/2;
  RandAccessIter target = begin + middleIdx;
  std::nth_element(begin, target, end);

  if(size % 2 != 0){ //Odd number of elements
    return *target;
  }else{            //Even number of elements
    double a = *target;
    RandAccessIter targetNeighbor= target-1;
    std::nth_element(begin, targetNeighbor, end);
    return (a+*targetNeighbor)/2.0;
  }
}
34
Eponymous

Voici une version plus complète de la réponse de Mike Seymour:

// Could use pass by copy to avoid changing vector
double median(std::vector<int> &v)
{
  size_t n = v.size() / 2;
  std::nth_element(v.begin(), v.begin()+n, v.end());
  int vn = v[n];
  if(v.size()%2 == 1)
  {
    return vn;
  }else
  {
    std::nth_element(v.begin(), v.begin()+n-1, v.end());
    return 0.5*(vn+v[n-1]);
  }
}

Il gère les entrées de longueur paire ou impaire.

10
Alec Jacobson

Cet algorithme gère efficacement les entrées paires et impaires en utilisant l'algorithme STL nth_element (amorti O(N)) et l'algorithme max_element (O (n)). Notez que nth_element a un autre effet secondaire garanti , à savoir que tous les éléments avant n sont tous garantis inférieurs à v[n], mais pas nécessairement trié.

//post-condition: After returning, the elements in v may be reordered and the resulting order is implementation defined.
double median(vector<double> &v)
{
  if(v.empty()) {
    return 0.0;
  }
  auto n = v.size() / 2;
  nth_element(v.begin(), v.begin()+n, v.end());
  auto med = v[n];
  if(!(v.size() & 1)) { //If the set size is even
    auto max_it = max_element(v.begin(), v.begin()+n);
    med = (*max_it + med) / 2.0;
  }
  return med;    
}
7
Matthew Fioravante

Vous pouvez trier un std::vector en utilisant la fonction de bibliothèque std::sort.

std::vector<int> vec;
// ... fill vector with stuff
std::sort(vec.begin(), vec.end());
4
Charles Salvia

rassemblant toutes les idées de ce fil, j'ai fini par avoir cette routine. il fonctionne avec n'importe quel conteneur stl ou n'importe quelle classe fournissant des itérateurs d'entrée et gère les conteneurs de tailles impaires et paires. Il fait également son travail sur une copie du conteneur, pour ne pas modifier le contenu d'origine.

template <typename T = double, typename C>
inline const T median(const C &the_container)
{
    std::vector<T> tmp_array(std::begin(the_container), 
                             std::end(the_container));
    size_t n = tmp_array.size() / 2;
    std::nth_element(tmp_array.begin(), tmp_array.begin() + n, tmp_array.end());

    if(tmp_array.size() % 2){ return tmp_array[n]; }
    else
    {
        // even sized vector -> average the two middle values
        auto max_it = std::max_element(tmp_array.begin(), tmp_array.begin() + n);
        return (*max_it + tmp_array[n]) / 2.0;
    }
}
3
Croc Dialer

Il existe un algorithme de sélection à temps linéaire . Le code ci-dessous ne fonctionne que lorsque le conteneur a un itérateur à accès aléatoire, mais il peut être modifié pour fonctionner sans - vous devrez juste être un peu plus prudent pour éviter les raccourcis comme end - begin et iter + n.

#include <algorithm>
#include <cstdlib>
#include <iostream>
#include <sstream>
#include <vector>

template<class A, class C = std::less<typename A::value_type> >
class LinearTimeSelect {
public:
    LinearTimeSelect(const A &things) : things(things) {}
    typename A::value_type nth(int n) {
        return nth(n, things.begin(), things.end());
    }
private:
    static typename A::value_type nth(int n,
            typename A::iterator begin, typename A::iterator end) {
        int size = end - begin;
        if (size <= 5) {
            std::sort(begin, end, C());
            return begin[n];
        }
        typename A::iterator walk(begin), skip(begin);
#ifdef RANDOM // randomized algorithm, average linear-time
        typename A::value_type pivot = begin[std::Rand() % size];
#else // guaranteed linear-time, but usually slower in practice
        while (end - skip >= 5) {
            std::sort(skip, skip + 5);
            std::iter_swap(walk++, skip + 2);
            skip += 5;
        }
        while (skip != end) std::iter_swap(walk++, skip++);
        typename A::value_type pivot = nth((walk - begin) / 2, begin, walk);
#endif
        for (walk = skip = begin, size = 0; skip != end; ++skip)
            if (C()(*skip, pivot)) std::iter_swap(walk++, skip), ++size;
        if (size <= n) return nth(n - size, walk, end);
        else return nth(n, begin, walk);
    }
    A things;
};

int main(int argc, char **argv) {
    std::vector<int> seq;
    {
        int i = 32;
        std::istringstream(argc > 1 ? argv[1] : "") >> i;
        while (i--) seq.Push_back(i);
    }
    std::random_shuffle(seq.begin(), seq.end());
    std::cout << "unordered: ";
    for (std::vector<int>::iterator i = seq.begin(); i != seq.end(); ++i)
        std::cout << *i << " ";
    LinearTimeSelect<std::vector<int> > alg(seq);
    std::cout << std::endl << "linear-time medians: "
        << alg.nth((seq.size()-1) / 2) << ", " << alg.nth(seq.size() / 2);
    std::sort(seq.begin(), seq.end());
    std::cout << std::endl << "medians by sorting: "
        << seq[(seq.size()-1) / 2] << ", " << seq[seq.size() / 2] << std::endl;
    return 0;
}
2
ephemient

Voici une réponse qui prend en compte la suggestion de @MatthieuM. c'est-à-dire que ne modifie pas le vecteur d'entrée . Il utilise un seul tri partiel (sur un vecteur d'indices) pour les deux plages de cardinalité paire et impaire, tandis que les plages vides sont gérées avec des exceptions levées par la méthode at d'un vecteur:

double median(vector<int> const& v)
{
    bool isEven = !(v.size() % 2); 
    size_t n    = v.size() / 2;

    vector<size_t> vi(v.size()); 
    iota(vi.begin(), vi.end(), 0); 

    partial_sort(begin(vi), vi.begin() + n + 1, end(vi), 
        [&](size_t lhs, size_t rhs) { return v[lhs] < v[rhs]; }); 

    return isEven ? 0.5 * (v[vi.at(n-1)] + v[vi.at(n)]) : v[vi.at(n)];
}

Démo

2
Lorah Attkins