【问题标题】:Compute Median of Values Stored In Vector - C++?计算向量中存储的值的中位数 - 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) 时间内运行,你仍然需要弄清楚中间使用哪个迭代器,你仍然需要对两者进行平均如果长度是偶数,则取中间值。

标签: c++ vector median


【解决方案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];
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2023-04-04
          • 1970-01-01
          • 1970-01-01
          • 2016-05-19
          • 2017-03-11
          • 2013-05-10
          • 2020-12-01
          • 1970-01-01
          相关资源
          最近更新 更多