计算向量中存储的值的中位数 - 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<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++?的主要内容,如果未能解决你的问题,请参考以下文章