代码之家  ›  专栏  ›  技术社区  ›  sharkin

使用STL容器进行中值计算时,正确的方法是什么?

  •  36
  • sharkin  · 技术社区  · 16 年前

    假设我需要从1000000个随机数值序列中检索中值。

    但是 std::list

    如果使用 标准::列表

    我自己实施排序并与其他人一起进行排序是否更好。 std::vector 标准::列表 和使用 std::list::iterator

    8 回复  |  直到 7 年前
        1
  •  112
  •   Mike Seymour    14 年前

    任意随机访问容器(如 std::vector )可以按照标准进行排序 std::sort <algorithm> 标题。

    std::nth_element ; 这样做的排序足以将一个选定的元素放在正确的位置,但不能对容器进行完全排序。所以你可以找到这样的中间值:

    int median(vector<int> &v)
    {
        size_t n = v.size() / 2;
        nth_element(v.begin(), v.begin()+n, v.end());
        return v[n];
    }
    
        2
  •  40
  •   Eponymous    16 年前

    中位数比迈克·西摩的答案更复杂。中位数的不同取决于样本中的项目数是偶数还是奇数。如果项目数为偶数,则中位数为中间两个项目的平均数。这意味着整数列表的中值可以是分数。最后,空列表的中位数是未定义的。下面是通过我的基本测试用例的代码:

    ///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;
      }
    }
    
        3
  •  14
  •   Matthew Fioravante    10 年前

    n 都保证低于 v[n] ,只是不一定要分类。

    //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;    
    }
    
        4
  •  11
  •   Alec Jacobson    11 年前

    以下是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]);
      }
    }
    

    它处理奇数或偶数长度输入。

        5
  •  6
  •   Croc Dialer    10 年前

    把这篇文章中的所有见解放在一起,我就有了这个惯例。它与任何stl容器或任何提供输入迭代器的类一起工作,并处理奇数和偶数大小的容器。它还处理容器的副本,以不修改原始内容。

    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;
        }
    }
    
        6
  •  4
  •   Charles Salvia    16 年前

    你可以把一个 std::vector 使用库函数 std::sort

    std::vector<int> vec;
    // ... fill vector with stuff
    std::sort(vec.begin(), vec.end());
    
        7
  •  2
  •   ephemient    16 年前

    存在一个 linear-time selection algorithm . 下面的代码仅在容器具有随机访问迭代器时有效,但是可以修改它使其在没有随机访问迭代器的情况下工作-您只需稍微小心一点,以避免像这样的快捷方式 end - begin 和 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;
    }
    
        8
  •  2
  •   Lorah Attkins    9 年前

    不修改输入向量 . 它对偶数和奇数基数的范围使用单个部分排序(在索引向量上),而空范围则使用向量的 at 方法:

    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)];
    }
    

    Demo

        9
  •  1
  •   Alessandro Jacopson    6 年前

    Armadillo 具有与答案中的实现类似的实现 https://stackoverflow.com/a/34077478 通过 https://stackoverflow.com/users/2608582/matthew-fioravante

    nth_element 还有一个电话 max_element https://gitlab.com/conradsnicta/armadillo-code/-/blob/9.900.x/include/armadillo_bits/op_median_meat.hpp#L380

    //! find the median value of a std::vector (contents is modified)
    template<typename eT>
    inline 
    eT
    op_median::direct_median(std::vector<eT>& X)
      {
      arma_extra_debug_sigprint();
      
      const uword n_elem = uword(X.size());
      const uword half   = n_elem/2;
      
      typename std::vector<eT>::iterator first    = X.begin();
      typename std::vector<eT>::iterator nth      = first + half;
      typename std::vector<eT>::iterator pastlast = X.end();
      
      std::nth_element(first, nth, pastlast);
      
      if((n_elem % 2) == 0)  // even number of elements
        {
        typename std::vector<eT>::iterator start   = X.begin();
        typename std::vector<eT>::iterator pastend = start + half;
        
        const eT val1 = (*nth);
        const eT val2 = (*(std::max_element(start, pastend)));
        
        return op_mean::robust_mean(val1, val2);
        }
      else  // odd number of elements
        {
        return (*nth);
        }
      }
    
        10
  •  0
  •   Kuldip Ghotane    6 年前
    you can use this approch. It also takes care of sliding window.
    Here days are no of trailing elements for which we want to find median and this makes sure the original container is not changed
    
    
    #include<bits/stdc++.h>
    
    using namespace std;
    
    int findMedian(vector<int> arr, vector<int> brr, int d, int i)
    {
        int x,y;
        x= i-d;
        y=d;
        brr.assign(arr.begin()+x, arr.begin()+x+y);
    
    
        sort(brr.begin(), brr.end());
    
        if(d%2==0)
        {
            return((brr[d/2]+brr[d/2 -1]));
        }
    
        else
        {
            return (2*brr[d/2]);
        }
    
        // for (int i = 0; i < brr.size(); ++i)
        // {
        //     cout<<brr[i]<<" ";
        // }
    
        return 0;
    
    }
    
    int main()
    {
        int n;
        int days;
        int input;
        int median;
        int count=0;
    
        cin>>n>>days;
    
        vector<int> arr;
        vector<int> brr;
    
        for (int i = 0; i < n; ++i)
        {
            cin>>input;
            arr.push_back(input);
        }
    
        for (int i = days; i < n; ++i)
        {
            median=findMedian(arr,brr, days, i);
    
            
        }
    
    
    
        return 0;
    }