std::set
是一个排序树。它提供了begin
和end
方法,因此我可以获得最小值和最大值,lower_bound
和upper_bound
用于二进制搜索。但是如果我想让迭代器指向中间元素(或者如果那里有偶数个元素的话,那么该怎么办?)
这样做是否有效(O(log(size))
而非O(size)
)?
{1} => 1
{1,2} => 1 or 2
{1,2,3} => 2
{1,2,3,4} => 2 or 3 (but in the same direction from middle as for {1,2})
{1,312,10000,14000,152333} => 10000
答案 0 :(得分:16)
根据您插入/删除项目的频率与查找中间/中间值的比例,可能比明显的解决方案更有效的解决方案是将持久性迭代器保留到中间元素并在每次插入/删除项目时更新它组。有一堆边缘情况需要处理(奇数与偶数项目,删除中间项目,空集等),但基本的想法是当你插入一个小于当前的中间项,你的中间迭代器可能需要递减,而如果你插入一个更大的,你需要递增。这是删除的另一种方式。
在查找时,这当然是O(1),但在每次插入/删除时也有基本上O(1)的成本,即N次插入后的O(N),需要在足够的时间内摊销查找次数使其比暴力强制更有效。
答案 1 :(得分:6)
获得二叉搜索树的中间部分将是O(大小)。您可以使用std::advance()
获取它,如下所示:
std::set<int>::iterator it = s.begin();
std::advance(it, s.size() / 2);
答案 2 :(得分:3)
取决于插入/删除项目与查找中间/中位数的频率之间的关系,比显而易见的方法更有效的解决方案是保持一个持久迭代器到中间元素,并在您每次从中间元素中插入/删除项目时对其进行更新。组。有很多边缘情况需要处理(奇数项与偶数项,删除中间项,空集等),但是基本思想是,当您插入小于当前中间项的项时,您的中间迭代器可能需要减少,而如果您插入一个较大的迭代器,则需要增加。这是清除的另一种方式。
void balance(multiset<int> &small, multiset<int> &big)
{
while (true)
{
int ssmall = small.size();
int sbig = big.size();
if (ssmall == sbig || ssmall + 1 == sbig) break; // OK
if (ssmall < sbig)
{
// big to small
auto v = big.begin();
small.emplace(*v);
big.erase(v);
}
else
{
// small to big
auto v = small.end();
--v;
big.emplace(*v);
small.erase(v);
}
}
}
auto medium = big.begin();
cout << *medium << endl;
auto v = big.begin();
if (v != big.end() && new_item > *v)
big.emplace(new_item );
else
small.emplace(new_item );
balance(small, big);
答案 3 :(得分:0)
请注意,std::set
不会存储重复的值。如果插入以下值{1, 2, 3, 3, 3, 3, 3, 3, 3}
,则将检索的中位数为2
。
std::set<int>::iterator it = s.begin();
std::advance(it, s.size() / 2);
int median = *it;
如果要在考虑中位数时包括重复项,则可以使用std::multiset
({1, 2, 3, 3, 3, 3, 3, 3, 3}
中位数为3
):
std::multiset<int>::iterator it = s.begin();
std::advance(it, s.size() / 2);
int median = *it;
如果您希望对数据进行排序的唯一原因是获取中位数,我认为最好使用普通的std::vector
+ std::sort
。
使用大量测试样本和多次迭代,我用std::vector
和std::sort
在5秒钟内完成了测试,并用std::set
或std::multiset
在13至15s内完成了测试。您的里程可能会有所不同,具体取决于您拥有的重复值的大小和数量。
答案 4 :(得分:0)
正如@pmdj 所说,我们使用迭代器来跟踪中间元素。以下是以下代码的实现:
class RollingMedian {
public:
multiset<int> order;
multiset<int>::iterator it;
RollingMedian() {
}
void add(int val) {
order.insert(val);
if (order.size() == 1) {
it = order.begin();
} else {
if (val < *it and order.size() % 2 == 0) {
--it;
}
if (val >= *it and order.size() % 2 != 0) {
++it;
}
}
}
double median() {
if (order.size() % 2 != 0) {
return double(*it);
} else {
auto one = *it, two = *next(it);
return double(one + two) / 2.0;
}
} };
请随意复制和使用此代码的任何部分。此外,如果不存在重复,您可以使用 set 而不是 multiset。