如何跟踪整数变化向量的中位数?

时间:2013-10-04 17:54:49

标签: c++ algorithm stl

尝试在http://www.hackerearth.com/problem/algorithm/sum-of-medians-1/解决问题,并考虑使用multiset解决问题,因为它可能包含重复值。 我尝试编码如下:

#include<iostream>
#include<set>
#include<algorithm>
using namespace std;
int main()
{
  int n,k,med_sum=0,p;
  cin>>n;
  multiset<int> m;
  multiset<int>::iterator itr;
  for(int i=0;i<n;i++)
  {
    cin>>k;
    m.insert(k);
    p=k;
    if(p<=k)
      itr--;
    else
      itr++;
    med_sum+=*itr;
  }
  cout<<med_sum<<endl;
  return 0;
}

Sample Input:
n=5
10
5
1
2
15
Sample Output: 27
Explanation:
m(1)=median of [10]=10
m(2)=median of [10 5]=5
m(3)=median of [10 5 1]=5
m(4)=median of [10 5 1 2 15]=5
med_sum=10+5+5+2+5=27

3 个答案:

答案 0 :(得分:6)

一种简单的方法是维护两个已分类的容器,一个低于中位数,一个更大。

当你找到一个新元素时,看看要插入哪个容器(这样下面的所有元素总是小于或等于上面的所有元素),然后重新平衡计数,使中位数“在间隙中”他们之间。

您可以这样做,但将较低范围定义为[.begin(), it),将较高范围定义为[it, .end())。除非性能特别重要,否则我会将它们设置为单独的容器,以减少需要保持头脑以理解代码的状态。

使用以下不变量维护两个已排序的容器lowhigh

  • low.size()high.size()或1更大
  • 相同
  • low的每个元素都小于或等于high的每个元素。 然后,low union high的中位数为low.last()

假设你有这样一对容器,如果你想添加一个元素x,我会首先保持不变2 - 如果low.empty()x <= low.last(),请将其粘贴在{ {1}},否则请将其粘贴在low

接下来,保持不变1:如果high包含的元素多于低,请从high中移除最低元素并将其添加到high。如果low的元素多于low,则删除high中的最高元素,并将其添加到low

如果我们从遵循上述两个不变量的highlow开始,那么在完成上述步骤后,我们仍然有highlow服从这两个不变量不变量。当上述两个不变量成立时,中位数为high

答案 1 :(得分:3)

您似乎尝试做的事情(错误地,使用您发布的代码)将值保存在多重集中,迭代器指向中间值,并在每次添加到集合时调整该指针,具体取决于是否您要添加的值高于或低于中位数。

这是一个很好的设计,可能是解决这个问题的最快方法。但是,它不适用于std::multiset,因为std::multiset的限制会导致一个关键角落问题 - 当添加到集合中的新值等于旧的中位数时,无法知道它是在多重集中的旧中位数之前还是之后插入的。所以你不知道在这种情况下如何调整中值指针。

因此,如果您想以这种方式解决问题,您需要以一种可以为您提供这一重要信息的方式实现您自己的rbtree或其他集合抽象。或者,当插入一个等于旧中位数的值时,你可以从头开始重新计算中位数(这是一项昂贵的操作,但应该很少见。)

修改

提示OP使代码适用于C ++ 11多字节:

  • 将第一个值插入多集时,需要初始化itr

  • 您只需在itr的“错误”一侧插入后调整itr。你可以根据多重大小是奇数还是偶数来判断哪一方是“错误的”。

  • 因为itr您想要测试if (k < *itr)而不是<=

答案 2 :(得分:1)

使用std::vector怎么样?通过插入std::lower_bound告诉您的位置来保持值的排序。

    std::vector<int> p;
    int k, med_sum;
    while(cin >> k)
    {
      p.insert(std::lower_bound(p.begin(), p.end(), k), k);
      med_sum += p[p.size()/2];
    }
    std::cout << med_sum << std::endl;

修改
使用向量的优点是算法非常清晰简洁(只有3行逻辑)和vector内存的缓存局部性以及大容量内存分配使其至少具有竞争力{ {1}} / mapset实施,即使其插入为list

为了进行比较,我使用O(n)编写了一个(未经测试的)实现:

multiset

7行逻辑并不坏,但它肯定不太清楚发生了什么,我不能仅仅通过观察它是否正确。请注意,此算法仅适用于 std::multiset<int> p; int k, med_sum; cin >> med_sum; p.insert(med_sum); std::multiset<int>::iterator itr = p.begin(); while(cin >> k) { if(p.size() % 2 == 0) { if(k < *itr) ++itr; } else { if(k < *itr) --itr; else ++itr; } med_sum += *itr; } cout << med_sum << endl; ,其中C++11在范围的顶端(重复项)插入重复值。

总而言之,我认为multimap::insert实施是更好的方法,除非必要性和衡量标准与vector

一致