如果方法是常量,如何找到向量的中位数?

时间:2019-04-20 20:25:45

标签: c++ algorithm sorting const median

我创建了一种名为Collect的方法,该方法将一堆值添加到向量中(如下所示)

void Median::Collect(double datum)
{
  myVector.push_back(datum);
}

我需要创建一种方法来计算我在上述方法中的向量中收集的所有值的中值。函数定义写在下面

/* Calculates the median of the data (datum) from the Collect method.
 */
 double Median::Calculate() const
{

}

所以我知道我首先需要对向量进行排序才能找到中位数。以下是我的尝试:

    double Median::Calculate() const
  {
    std::sort(myVector.begin(), myVector.end());
    double median;
    if (myVector.size() % 2 == 0)
    {// even
        median = (myVector[myVector.size() / 2 - 1] + myVector[myVector.size() / 2]) / 2;
    }
    else
    {// odd
        median = myVector[myVector.size() / 2];
    }
    return median;
  }

但是我意识到,由于该方法是const的,因此无法编译,因此对向量的值进行排序会更改向量,这在const函数中是不允许的。那么我应该为这种方法做什么?

3 个答案:

答案 0 :(得分:11)

复制myVector,对其进行排序,然后计算其中位数。

与仅使用std::sort相比,我们可以做得更好。我们不需要对向量进行完全排序即可找到中值。我们可以使用std::nth_element查找中间元素。由于具有偶数个元素的向量的中位数是中间两个元素的平均值,因此在这种情况下,我们需要做更多的工作才能找到另一个中间元素。 std::nth_element确保中间位置之前的所有元素都小于中间位置。它不能保证它们的顺序超出此范围,因此我们需要使用std::max_element来找到中间元素之前的最大元素。

您可能没有考虑的另一件事是myVector为空的情况。查找空向量的中位数实际上没有任何意义。在此示例中,我仅使用了assert,但是您可能想抛出异常或其他东西。

double Median::calculate() const {
  assert(!myVector.empty());
  std::vector<double> myVectorCopy = myVector;
  const auto middleItr = myVectorCopy.begin() + myVectorCopy.size() / 2;
  std::nth_element(myVectorCopy.begin(), middleItr, myVectorCopy.end());
  if (myVectorCopy.size() % 2 == 0) {
    const auto leftMiddleItr = std::max_element(myVectorCopy.begin(), middleItr);
    return (*leftMiddleItr + *middleItr) / 2.0;
  } else {
    return *middleItr;
  }
}

另一种选择是使用其他容器以确保元素始终被排序。您可以考虑使用std::set。当您插入std::set时,该集合将保持排序状态,因此不必使用std::sortstd::nth_elementstd::max_element来查找中位数。您将获得中间元素。

答案 1 :(得分:-1)

const方法是只能在其所属类的const实例上调用的方法。因此,如果您已经声明了一个类Median并在其上声明了一个const方法,那么它只能与const类的Median实例一起调用。不可能影响其他类,例如std::vector

无论如何,如果您决定从std::vector派生一个新类并考虑向其中添加一种方法median来计算中位数,则最好将其声明为{{1} } 。这样做的原因是,您无需修改​​数组即可获得其中间值(请参见下文)。

如果需要对数组进行排序,则可以进行复制,甚至更好,可以考虑使用指向数组元素的指针数组,然后根据指向的值对数组进行排序,然后考虑该数组的中心元素。这样,您就不会接触原始实例,并且仍然可以维护该方法的const属性。

答案 2 :(得分:-3)

您可以将myVector声明为mutable。即使您使用的是const函数,这也将允许数据进行更改。

如果出于某种原因(这不是一种选择),则可以考虑使用某种数据类型来保持数据排序并在正确的位置插入新数据。这样,您将不需要在每次运行此功能时对其进行排序,但是会降低插入速度。考虑一下将要发生的事情:插入或获取中位数。


更困难的方法是同时兼顾两者。向量将保持不变,并且同一函数的第二次运行实际上将比第一次运行更快地返回答案。

然后您可以执行以下操作:

// Median.hpp
class Median
{
  std::vector<double> myVector;
  mutable double median;
  mutable bool medianCalculated;
// the rest is the same
};

// Median.cpp
double Median::calculate() const
{
  if(!medianCalculated)
  {
    std::vector<double> copyVector = myVector;
    std::sort(copyVector.begin(), copyVector.end();
    const auto m1 = copyVector.begin() + (copyVector.size() / 2);
    const auto m2 = copyVector.begin() + ((copyVector.size() + 1) / 2);
    median = (*m1 + m2) / 2; // m1==m2 for even sized vector m1+1==m2 for odd sized
    medianCalculated=true;
  }
  return median;  
}
void Median::Collect(double datum)
{
  myVector.push_back(datum);
  medianCalculated=false;
}