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

sha*_*kin 40 c++ algorithm containers stl median

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

如果使用任何但是 STL ::名单,我没有(内置)的排序方式为中值计算序列.

如果使用STL :: list,我不能随机访问值来检索排序序列的中间(中位数).

是自己实现排序和使用例如STL :: vector更好,还是使用STL :: list并使用STL :: list :: iterator for-loop-walk到中值?后者似乎不那么开销,但也感觉更难看..

或者我有更多更好的选择吗?

Mik*_*our 87

任何随机访问容器(如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];
}
Run Code Online (Sandbox Code Playgroud)

  • 如果项目数是偶数,则中位数是中间***的平均值. (36认同)
  • 应该注意,`nth_element`以不可预测的方式修改向量!如有必要,您可能希望对索引向量进行排序. (5认同)
  • @ sje397是的,这个算法有一半的时间是不正确的,即当向量包含偶数个元素时.调用nth_element函数2次(对于2个中间元素)比调用sort一次更昂贵吗?谢谢. (3认同)
  • 呵呵。我没有意识到 `nth_element` 存在,我显然在我的回答中重新实现了它...... (2认同)

Epo*_*ous 33

中位数比Mike Seymour的答案更复杂.中位数取决于样本中是偶数还是奇数项.如果项目数为偶数,则中位数是中间两项的平均值.这意味着整数列表的中位数可以是一小部分.最后,空列表的中位数未定义.这是通过我的基本测试用例的代码:

///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;
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 我知道这是从很久以前开始的,但是因为我刚刚在google上找到了这个:`std :: nth_element`实际上也保证了任何前面的元素<=目标,任何后面的元素都是> =.所以你可以使用`targetNeighbor = std :: min_element(begin,target)`并跳过部分排序,这可能会快一点.(`nth_element`是平均线性的,而`min_element`显然是线性的.)即使你再次使用`nth_element`,它也是等效的,并且可能更快一点,只需做`nth_element(开始, targetNeighbor,target)`. (16认同)
  • @Dougal我认为你的意思是`targetNeighbor = std :: max_element(begin,target)`在这种情况下? (9认同)
  • @tobi303 你的永远是我的两倍。:) 是的,它绝对应该:重点是在调用 `std::nth_element` 之后,序列就像 `[smaller_than_target, target,biger_than_target]`。所以你知道第一个“target-1”元素位于数组的前半部分,你只需要找到“target”之前元素的最大值即可获得中位数。 (2认同)

Ale*_*son 10

这是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]);
  }
}
Run Code Online (Sandbox Code Playgroud)

它处理奇数或偶数长度的输入.

  • 与其第二次调用 `nth_element`,而是从 `v[0]` 迭代到 `v[n]` 并确定那一半的最大值不是更有效吗? (3认同)

Mat*_*nte 7

该算法使用STL nth_element(分摊的O(N))算法和max_element算法(O(n))有效地处理偶数和奇数大小的输入.请注意,nth_element具有另一个保证的副作用,即之前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;    
}
Run Code Online (Sandbox Code Playgroud)


小智 6

把这个线程的所有见解放在一起,我最终有了这个例程。它适用于任何 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @trig-ger nth_element 在此解决方案中仅使用一次。这不成问题。 (4认同)