Running Median (对顶堆)
题意
题目链接:[https://ac.nowcoder.com/acm/contest/60932/O]()
包含多组输入,每组输入首先会给出两个空格分隔的整数,分别为组数编号和该组包含的元素个数。下一行就是该组的所有元素。输出要求每当输入第奇数个元素时,输出现有元素的中位数。
样例
输入
1
1 5
1 2 3 4 5
输出
1 2 3
题目保证了每组数据的长度都为奇数。
题解
这个求中位数是动态的,就像他的名字,Running Median,所以跟一般的求中位数不一样。这里用的是对顶堆,就是两个堆,一个小顶堆,一个大顶堆。小顶堆就是队头比较小,然后依次增大,大顶堆就是队头比较大,然后依次减小。为什么要用对顶堆?因为我们求中位数都需要排序吧,然后这个对顶堆实现了排序这个功能,并且把一组数据分成了两部分,平均分的,所以中位数就很好找,要么在小顶堆的队头,要么在大顶堆的队头(取决你自己怎么选)。
我先口述一下怎么实现的吧。这里拿小顶堆队头举例(因为我觉得这更符合正常思维)。首先把第一个数字推入小顶堆,由于小顶堆是从小往大排,所以要把所有比小顶堆队头大的都推入队列,然后把比小顶堆队头小的都推入大顶堆队列里。后面我先不讲,在这里停一下。这两步操作是不是就实现了我前面说的,把一组数据分成两部分,然后中位数就在小顶堆队头,你认真想一想,很关键。接着前面来,如果我把所有比小顶堆队头大的都推入队列了,那小顶堆队头还是中位数吗?显然不是。小顶堆队头是中位数当且仅当队头右边的个数等于大顶堆里面元素的个数(这也很关键)。那要如何实现呢?很简单,这其实就是说小顶堆的大小必须比大顶堆大1。所以直接在后面判断一下,如果小顶堆的大小比大顶堆小了,那就从大顶堆的队头开始一个一个拿到小顶堆,为什么是从队头?因为大顶堆是从大到小排序,大顶堆的队头是最接近小顶堆的队头的。还要在判断一下,如果小顶堆的大小比大顶堆的大小加一还大,那也不行,那就要从小顶堆的队头开始一个一个拿到大顶堆里去。好,到这就基本完成了。看代码吧。
代码
我的代码是用大顶堆队头写的,所以跟刚刚讲的有点不一样,但是原理都是一样的。
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 5 6 int main() 7 { 8 int t, num, n, x; 9 scanf("%d", &t); 10 while (t--) 11 { 12 priority_queue<int, vector<int>, greater<int> > q1; //小顶堆 13 priority_queue<int, vector<int>, less<int> > q2;//大顶堆 14 scanf("%d%d", &num, &n); 15 printf("%d %d\n", num, (n + 1) >> 1); 16 17 for (int i = 1; i <= n; i ++){ 18 scanf("%d", &x); 19 20 if (q2.empty() || x < q2.top())q2.push(x); 21 else q1.push(x); 22 23 while (q2.size() < q1.size()){ 24 q2.push(q1.top()); 25 q1.pop(); 26 } 27 while (q2.size() > q1.size() + 1){ 28 q1.push(q2.top()); 29 q2.pop(); 30 } 31 32 if (i & 1){ 33 printf("%d", q2.top()); 34 //cout << i << endl; 35 if ((i + 1) % 20 == 0 || i == n)puts(""); 36 else printf(" "); 37 } 38 } 39 } 40 return 0; 41 }

浙公网安备 33010602011771号