剑指Offer 41—数据流中的中位数

力扣

题意

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。

例如:

[2,3,4] 的中位数是 3;

[2,3] 的中位数是 (2 + 3) / 2 = 2.5

设计一个支持以下两种操作的数据结构:

  • void addNum(int num) :从数据流中添加一个整数到数据结构中。
  • double findMedian() :返回目前所有元素的中位数。

剑指Offer 41—数据流中的中位数_第1张图片

 解题思路

题目要求获取一个数据流排序后的中位数,那么可以使用两个优先队列(堆)实现。

题使用一个大顶堆,一个小顶堆完成。

  • 大顶堆的每个节点的值大于等于左右孩子节点的值,堆顶为最大值
  • 小顶堆的每个节点的值小于等于左右孩子节点的值,堆顶为最小值

因此我们使用 大顶堆(maxHeap) 来存储数据流中较小一半的值,用 小顶堆(minHeap) 来存储数据流中较大一半的值。大顶堆的堆顶”与“小顶堆的堆顶”就是排序数据流的两个中位数

如图所示,大顶堆(maxHeap)置于下方,小顶堆(minHeap)倒置于上方,两个堆组合起来像一个沙漏的形状。剑指Offer 41—数据流中的中位数_第2张图片

根据堆的性质,且大顶堆存放的是较小一半的数值,小顶堆存放较大一半的数值,可以判断两个堆的值从下往上递增,即:

maxHeap堆底 <= maxHeap堆顶 <= minHeap堆顶 <= minHeap堆底。 


题目要求获取数据流排序后的中位数,而根据数据流的奇偶性以及堆的性质,将获取中位数的情况分为两类:

  1. 数据流为奇数时,保证两个堆的长度相差1,那么长度较大的堆的堆顶就是中位数;
  2. 数据流为偶数时,保证两个堆的长度相等,两个堆的堆顶相加除二就是中位数。

那么我们要保证每次插入元素后,两个堆要 维持相对长度。让minHeap为长度较大的堆,每次插入元素时进行判断:

  • 当两堆总长度为偶数时,即两堆长度相等,将新元素插入到minHeap,插入后minHeap比maxHeap长度大1;
  • 当两堆总长度为奇数时,即两堆长度不等,将新元素插入到maxHeap,插入后两堆长度相等;

还要保证插入元素后两堆仍是保证从下往上递增的顺序性。如上面的偶数情况,将新元素x直接插入到minHeap,是有可能破坏两堆的顺序性的,例如:(minHeap是存储“较大一半”的值)

  • 若x的值恰好为“较大一半”,直接将插入到“较大一半”的minHeap中,是不会破坏顺序的;
  • 若x的值为“较小一半”,而此时却插入到“较大一半”的minHeap中,是会破坏顺序的。

那么,每次新元素插入时,都需要先插入到另一个堆,进行重新排序后,再将最值拿出来插入正确的堆中。因此,最终得出的结论为:

  • 当两堆总大小为偶数时,即两堆大小相等,先将新元素插入maxHeap,重新排序后将新的最值拿出并插入到minHeap(这样可以保证插入到minHeap的值是较大一半的值)
  • 当两堆总大小为奇数时,即两堆大小不等,先将新元素插入minHeap,重新排序后将新的最值拿出并插入到maxHeap(这样可以保证插入到maxHeap的值是较小一半的值)

讲讲C++的priority_queue

priority_queue 优先级队列,只能“从一端进(称为队尾),从另一端出(称为队头)”,且每次只能访问 priority_queue 中位于队头的元素。但是 先进队列的元素并不一定先出队列,而是优先级最大的元素最先出队列。 

每个 priority_queue 容器适配器在创建时,都制定了一种排序规则。根据此规则,该容器适配器中存储的元素就有了优先级高低之分。
举个例子,假设当前有一个 priority_queue 容器适配器,其制定的排序规则是按照元素值从大到小进行排序。根据此规则,自然是 priority_queue 中值最大的元素的优先级最高。


STL中,priority_queue容器适配器定义如下:

template ,typename Compare=std::less >
class priority_queue
{
    //......
}
  • typename T:指定存储元素的具体类型;
  • typename Compare:指定容器中评定元素优先级所遵循的排序规则,默认使用std::less按照元素值从大到小进行排序(即默认是大顶堆),还可以使用std::greater按照元素值从小到大排序(小顶堆),但更多情况下是使用自定义的排序规则。
  • typename Container:指定 priority_queue 底层使用的基础容器,默认使用 vector 容器。
    • 作为 priority_queue 容器适配器的底层容器,其必须包含 empty()、size()、front()、push_back()、pop_back() 这几个成员函数, STL序列式容器中只有 vector 和 deque 容器符合条件。

自定义排序

无论 priority_queue 中存储的是基础数据类型(int、double 等),还是 string 类对象或者自定义的类对象,都可以使用函数对象的方式自定义排序规则。例如:

#include
#include
using namespace std;
//函数对象类
template 
class cmp
{
public:
    //重载 () 运算符
    bool operator()(T a, T b)
    {
        return a > b;
    }
};

int main()
{
    int a[] = { 4,2,3,5,6 };
    priority_queue,cmp > pq(a,a+5);
    while (!pq.empty())
    {
        cout << pq.top() << " ";
        pq.pop();
    }
    return 0;
}

C++实现

class MedianFinder 
{
private:
    priority_queue,less> max_heap; //大顶堆,存放较小一半的数值
    priority_queue,greater> min_heap ;//小顶堆,存放较大一半的数值

public:
    /** initialize your data structure here. */
    MedianFinder() 
    {

    }
    
    void addNum(int num) 
    {
        //如果 大顶堆和小顶堆的数值相等,就先将数值插到大顶堆里面,再从大顶堆的顶端拿出元素插到小顶堆里面
        if(max_heap.size()==min_heap.size())
        {
            max_heap.push(num);
            min_heap.push(max_heap.top());
            max_heap.pop();
        }
        else
        {
            min_heap.push(num);
            max_heap.push(min_heap.top());
            min_heap.pop();
        }
    }
    
    double findMedian() 
    {
        if(max_heap.size()==min_heap.size())
        {
            return (max_heap.top()+min_heap.top()) / (double)2;
        }
        else
        {
            return min_heap.top();
        }
    }
};

/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder* obj = new MedianFinder();
 * obj->addNum(num);
 * double param_2 = obj->findMedian();
 */

你可能感兴趣的:(剑指Offer,数据结构,leetcode,c++,算法,剑指offer)