剑指offer - 数据流中的中位数

题目

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

分析

由于数据是从一个数据流中读出来的,因而数据的数目随着时间的变化而增加。如果用一个数据容器来保存从流中读出来的数据,则当有新的数据从流中读出来时,这些数据就插入数据容器。那么该用什么数据容器呢?

  • 数组

数组是最简单的数据容器。如果数组没有排序,则可以用Partition函数找出中位数。

1、在没有排序的数组中插入一个数字和找出中位数的时间复杂度分别是O(1)和O(n)。
2、如果往数组里插入新数据时让数组保持排序,由于可能需要移动O(n)个数。因此需要O(n)时间才能完成插入操作。在已经排好序的数组中查找中位数,只需O(1)时间

  • 排序的链表

需要O(n)时间才能在链表中找到合适的位置插入。如果定义两个指针指向链表中间的结点,那么可以在O(1)时间得到中位数

  • 二叉搜索树

二叉搜索树可以把插入新数据的平均时间降低到O(logn),但是,当二叉搜索树极度不平衡从而看起来像一个排序的链表时,插入新数据的时间仍然是O(n)。为了得到中位数,可以在二叉树结点中添加一个表示子树结点数目的字段。有了这个字段,可以在平均O(logn)时间内得到中位数,但最差情况仍然需要O(n)

  • 平衡二叉树(AVL树)

为了避免二叉搜索树的最差情况,还可以利用平衡二叉树,即AVL树。通常AVL树的平衡因子是左、右子树的高度差。可以稍做修改,把AVL树的平衡因子改为左、右子树结点数目之差。可以用O(logn)时间往AVL树中添加一个新结点,用O(1)时间得到所有结点的中位数

  • 最大堆和最小堆
4png.png

如果能够保证数据容器左边的数据都小于右边的数据,那么即使左、右两边内部的数据没有排序,也可以根据左边最大的数和右边最小的数得到中位数。

用最大堆实现这个数据容器,因为位于堆顶的就是最大的数据,同样,也可以在最小堆中找出最小值。

用一个最大堆实现左边的数据容器,用一个最小堆实现右边的数据容器。往堆中插入一个数据的时间效率是O(logn)。由于只需要O(1)时间就可以得到位于堆顶的数据,因此,得到中位数的时间复杂度是O(1)。

1167904-20190112151510345-1764476192.png

算法实现

使用vector实现

template <typename T>
class DynamicArray {
public:
    // 插入数据
    void Insert(T number) {
        // 最小堆个数
        unsigned long minSize = min.size();
        // 最大堆个数
        unsigned long maxSize = max.size();

        // 为了实现平均分配,数据的总数据为偶数时插入最小堆
        if ((minSize + maxSize & 1) == 0) {
            // 插入元素小于最大值
            if (maxSize> 0 && number<max[0]) {
                // 添加到最大堆
                max.push_back(number);
                // 调整元素顺序成为最大堆
                push_heap(max.begin(), max.end(), less<T>());

                // 取出首元素最大值
                number = max[0];

                // 弹出堆顶元素
                pop_heap(max.begin(), max.end(), less<T>());
                max.pop_back();
            }

            // 插入最小堆
            min.push_back(number);
            // 调整最小堆
            push_heap(min.begin(), min.end(), greater<T>());
        } else { // 奇数时候,放入最大堆
            if (minSize > 0&& min[0] < number) {
                min.push_back(number);
                push_heap(min.begin(), min.end(), greater<T>());

                number=min[0];

                pop_heap(min.begin(), min.end(), greater<T>());
                min.pop_back();
            }

            // 最小值插入大根堆
            max.push_back(number);
            // 调整最大堆
            push_heap(max.begin(), max.end(), less<T>());
        }
    }

    // 获取中位数
    T GetMedian() {
        unsigned long size = min.size() + max.size();
        if (size==0) { // 没有元素,抛出异常
            throw "No number are available";
        }

        T median = 0; // 中位数
        if ((size & 1) == 1) { // 奇数个数
            median = min[0];
        } else { // 偶数个数
            median = (min[0] + max[0]) / 2;
        }

        return median;
    }

private:
    vector<T> min; // 最小堆
    vector<T> max; // 最大堆
};

使用priority_queue实现

class Solution {
public:
    // 插入数据
    void Insert(int num) {
        count += 1;

        if (count%2==0) { // 偶数个元素
            // 插入大顶堆
            bigHeap.push(num);
            // 获取大顶堆的最大画元素插入小顶堆
            smallHeap.push(bigHeap.top());
            // 弹出大顶堆首元素
            bigHeap.pop();
        } else {
            smallHeap.push(num);
            bigHeap.push(smallHeap.top());
            smallHeap.pop();
        }
    }

    // 获取中位数
    double GetMedian() {
        if (count & 0x1) {
            return bigHeap.top();
        } else {
            return (bigHeap.top() + smallHeap.top()) / 2.0;
        }
    }

private:
    int count = 0; // 元素个数
    priority_queue<int, vector<int>, less<int>> bigHeap; // 左边一个大顶堆
    priority_queue<int, vector<int>, greater<int>> smallHeap; // 右边一个小顶堆
};

参考

《剑指 offer》

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,099评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,473评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,229评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,570评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,427评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,335评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,737评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,392评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,693评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,730评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,512评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,349评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,750评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,017评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,290评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,706评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,904评论 2 335