计算向量中存储的值的中位数 - C++?

Posted

技术标签:

【中文标题】计算向量中存储的值的中位数 - C++?【英文标题】:Compute Median of Values Stored In Vector - C++? 【发布时间】:2011-01-08 01:24:33 【问题描述】:

我是一名编程学生,对于我正在从事的项目,我必须做的事情之一是计算 int 值向量的中值。我将仅使用 STL 中的排序函数和向量成员函数(例如 .begin().end().size())来执行此操作。

我还应该确保找到向量的中值是奇数还是偶数。

卡住了,下面我已经包含了我的尝试。那么我哪里错了?如果您愿意给我一些指示或资源以朝着正确的方向前进,我将不胜感激。

代码:

int CalcMHWScore(const vector<int>& hWScores)

     const int DIVISOR = 2;
     double median;
     sort(hWScores.begin(), hWScores.end());
     if ((hWScores.size() % DIVISOR) == 0)
     
         median = ((hWScores.begin() + hWScores.size()) + (hWScores.begin() + (hWScores.size() + 1))) / DIVISOR);
     
     else 
     
       median = ((hWScores.begin() + hWScores.size()) / DIVISOR)
     

    return median;

【问题讨论】:

我不确定在这里使用命名常量来表示“2”是否合适。 @Max - 谢谢你的收获,我给它加了标签。 您可能会收到一条多行长的错误消息,最终涉及“排序”行。那是因为您的函数的输入参数是 const 并且 sort 正在尝试修改其内容。通过按值而不是通过 const 引用传递 hWScores 来更改它。 告诉你的老师 partial_sort 因为它可以用来在 O(n) 时间内找到中位数。不需要人们建议的任何这些奇数/偶数长度检查。 Darid,使用 partial_sort 仍然会在 O(n log n) 时间内运行,你仍然需要弄清楚中间使用哪个迭代器,你仍然需要对两者进行平均如果长度是偶数,则取中间值。 【参考方案1】:

接受的答案使用std::sort,它做的工作比我们需要的要多。使用std::nth_element 的答案不能正确处理偶数大小的情况。


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

int CalcMHWScore(std::vector<int> hWScores) 
  assert(!hWScores.empty());
  const auto middleItr = hWScores.begin() + hWScores.size() / 2;
  std::nth_element(hWScores.begin(), middleItr, hWScores.end());
  if (hWScores.size() % 2 == 0) 
    const auto leftMiddleItr = std::max_element(hWScores.begin(), middleItr);
    return (*leftMiddleItr + *middleItr) / 2;
   else 
    return *middleItr;
  

您可能需要考虑返回 double,因为当向量具有偶数大小时,中位数可能是分数。

【讨论】:

【参考方案2】:

你正在做一个额外的划分,总体上使它比它需要的更复杂一些。此外,当 2 在上下文中实际上更有意义时,无需创建 DIVISOR。

double CalcMHWScore(vector<int> scores)

  size_t size = scores.size();

  if (size == 0)
  
    return 0;  // Undefined, really.
  
  else
  
    sort(scores.begin(), scores.end());
    if (size % 2 == 0)
    
      return (scores[size / 2 - 1] + scores[size / 2]) / 2;
    
    else 
    
      return scores[size / 2];
    
  

【讨论】:

等一下,我不应该在这里通过不断的引用传递,不是吗?因为那时函数无法对传递的向量进行排序。 正确,正如 Rob 和 Alexandros 指出的那样 - 我在复制代码时没有注意到这一点。已在上次编辑中修复。 如果你需要通过常量引用传递,那么你可以制作一个向量的本地副本,并对其进行排序。 排序的复杂度为 n log(n),而查找中位数可以是 log(n) 时间内的代码,不要使用排序查找大向量的中位数。 scrores.size() == 0 -> segfault, scores.size() == 1 - segfault【参考方案3】:

没有必要对向量进行完全排序:std::nth_element 可以做足够的工作将中位数放在正确的位置。例如,请参阅我对this question 的回答。

当然,如果你的老师禁止使用正确的工具来完成这项工作,那也无济于事。

【讨论】:

实际上应该使用nth_element 方法而不是排序,因为前者只需要 O(n) 时间,而后者需要 O(n log n)。 如前所述,您的解决方案仅在“大小”不均匀时才有效。 @Anonymous, nth_element 仍然适用于均匀大小。您只需调用nth_element 两次,即可将两个“中心”元素放置到位。 @AaronMcDaid 这真的有效吗?第二次调用nth_element 可以改变第一次调用确定的值的位置。数组永远不会同时将两个元素放在适当的位置;在进行第二次调用之前,您需要保存第一个值。 @MarkRansom,正如您所说,可以存储第一次调用的值。在第二次调用nth_element 函数后,您可以存储第二个值并计算中位数。您不需要将这些值放在向量中的正确位置。【参考方案4】:

我在下面给出了一个示例程序,它与 Max S. 的响应中的示例程序有些相似。为了帮助 OP 提高他的知识和理解,我进行了一些更改。我有:

a) 将通过 const 引用的调用更改为按值调用,因为 sort 将要更改向量中元素的顺序,(编辑:我刚刚看到 Rob Kennedy 在我准备我的发布)

b) 将 size_t 替换为更合适的 vector&lt;int>::size_type(实际上是后者的方便同义词),

c) 将 size/2 保存到中间变量,

d) 如果向量为空,则抛出异常,并且

e) 我还介绍了条件运算符 (? :)。

实际上,所有这些更正都直接来自 Koenig 和 Moo 的“Accelerated C++”第 4 章。

double median(vector<int> vec)

        typedef vector<int>::size_type vec_sz;

        vec_sz size = vec.size();
        if (size == 0)
                throw domain_error("median of an empty vector");

        sort(vec.begin(), vec.end());

        vec_sz mid = size/2;

        return size % 2 == 0 ? (vec[mid] + vec[mid-1]) / 2 : vec[mid];

【讨论】:

【参考方案5】:

我不确定你对 vector 成员函数的用户有什么限制,但使用 []at() 进行索引访问会使访问元素更简单:

median = hWScores.at(hWScores.size() / 2);

您也可以使用像 begin() + offset 这样的迭代器,就像您目前正在做的那样,但是您需要首先使用 size()/2 计算正确的偏移量并将其添加到 begin(),而不是相反。您还需要取消引用生成的迭代器以访问该点的实际值:

median = *(hWScores.begin() + hWScores.size()/2)

【讨论】:

【参考方案6】:
const int DIVISOR = 2;

不要这样做。它只会让你的代码更加复杂。您可能已经阅读过关于不使用幻数的指南,但是数字的偶数与奇数是一个基本属性,因此将其抽象出来并没有好处,反而会妨碍可读性。

if ((hWScores.size() % DIVISOR) == 0)

    median = ((hWScores.begin() + hWScores.size()) + (hWScores.begin() + (hWScores.size() + 1))) / DIVISOR);

您将一个迭代器带到向量的末尾,再将另一个指向向量末尾的迭代器,将迭代器相加(这不是一个有意义的操作),然后将结果相除迭代器(这也没有意义)。这是更复杂的情况;我将首先解释如何处理奇数大小的向量,然后将偶数大小的情况留给您练习。


else 

    median = ((hWScores.begin() + hWScores.size()) / DIVISOR)

再次,您正在划分一个迭代器。相反,您想要做的是通过hWScores.size() / 2 元素将迭代器递增到向量的开头:

    median = *(hWScores.begin() + hWScores.size() / 2);

请注意,您必须取消引用迭代器才能从中获取值。如果你使用索引会更直接:

    median = hWScores[hWScores.size() / 2];

【讨论】:

以上是关于计算向量中存储的值的中位数 - C++?的主要内容,如果未能解决你的问题,请参考以下文章

在R中顺序计算列的中位数并将值存储在数据框中

在 SQL 中分析并形成分位数并计算落在各个分位数中的值的百分比

值计数的百分位数

在二叉搜索树中查找中位数的错误

Matlab中每n个值的中位数

具有最大内存效率的增量中值计算