海里数据topk

点:海量数据往往意味着不能按部就班的排序,因为很可能内存根本装不下无法做原地比较排序

海量数据处理问题不限于topk问题的往往几个点:

1、hash系:绝非把全部数据装入一个hashmap之类东西再去处理,而是重在巧用,如bitmap、bloomfilter

2、线性排序系:空间换时间的方式表面看在海里数据时更糟糕,但桶排序的数据分布思想、基数排序的”基数“思想在处理某些海量数据问题时非常有效,如中位数、第K大这类

3、树系:直接用二叉排序树(avl/rb)做排序去解决注定糟糕或无法解决,但堆、胜败树这类结构,却也在大顶堆解决topk、中位数、多路归并排序这类解法中用到,尤其注意堆

4、排序系:表面看不可能了,但有个典型的是快速排序,在内存可接受不需担心原数据被破坏的前提之下,快速排序的分治策略在解决某些海量数据问题时也是一类有效方式

5、技巧系:就看能不能想的出来了......


对于topk,或者说是静态的topk,即数据不会变化,就是XX亿数据获取前K个,典型方式为:

一、用堆

1、用原始数据前K个创建一个小/大根堆,大根堆还是小根堆的堆由需求是从小到大还是从大到小决定;

2、如果是小根堆,那么原始数据后面的数据,比堆顶还小明显可以放弃了,比堆顶大的就替换堆顶触发重排堆顶,如果是大根堆就反过来

3、全部原始数据都遍历完了,那个堆里的数据就是最大/小的K个

时间复杂度O(N + k),空间复杂度O(k),不破坏原始数据


二、用快排

注意不是真的排完序再找

快排的每一趟排序之后,查看分治点和k的大小关系,和k一样当然最好直接得到结果,如果比k大,则接着排从头到分治点的部分,注意只需要继续排从头到分治点的,当发现比k小,则继续排从k到分治点的部分......直到和k一样大

时间复杂度未知,实践发现应该不会太慢的。不需要额外空间复杂度。注意会破坏原始的数据。


三、有条件的用bitmap

如果原始数据是正整数且是不重复的,那么用bitmap也非常好,需要额外的O(数据上限/32 + 1)个int的空间复杂度,大约是数据上限/8的字节数

创建一个容量为数据上限/8 + 1的int数组,之所以这样做是因为bitmap每个int数据可以存储32个数字,数据上限/32 + 1个int数据就可以表示数据上限个不同的正整数,

每个数据除以32得到的索引即是bitmap数组中这个数所在的int数据的索引,然后对原始数据求余数获取它应该在这个int数据里的位索引,set该位为1

最终求topk就是从头或从尾的bitmap数组去对应前k个数即可。

bitmap可能是topk里思路代码最简单的办法了。


四、用线性排序

桶排序,那么O(N)遍历一次后,根据每个桶内数据个数,再看求topk对应了哪些桶,这些桶内的相关数据就是topk,时间空间都是O(N)

基数排序,设定一个除数如65535并进而创建2^32/2^16 = 65536容量的数组,然后O(N)遍历一次每个元素数据除以基数65535,除数扔进数组内,遍历后,看所需的k需要对应多少个数组成员,然后二次遍历原始数据,专门查找除法结果为topk所对应的那些数组成员的原始数据,命中这些数组的原始数据就是要求的topk。空间复杂度65536个int,时间复杂度O(N * 2)


综上所述最好的办法还是堆的方式。代码包括前两种方式的。


代码:


用堆的:

#include <iostream>
#include <random>


template<class T> class Heap {
    T *data;
    int size;

    void swap (int i, int j) {
        T tmp = data[i];
        data[i] = data[j];
        data[j] = tmp;
    }

public:
    Heap (T *_data, int _size):size(_size) {
        data = new T[size + 1];
        for (int i = 1; i <= size; i++) {
            data[i] = _data[i - 1];
        }

        for (int i = size/2; i >= 1; i--) {
            adjust(i, size);
        }
    }
    ~Heap () {
        delete []data;
        size = 0;
    }

    void adjust (int idx, int sz) {
        if (idx <= sz/2) {
            T cur = data[idx], l = data[idx * 2], r = data[idx * 2 + 1], max = cur;
            int maxidx = idx;
            if ((idx * 2) <= sz && l > max) {
                max = l;
                maxidx = idx * 2;
            }
            if ((idx * 2 + 1) <= sz && r > max) {
                max = r;
                maxidx = idx * 2 + 1;
            }

            if (max != cur) {
                swap(maxidx, idx);
                adjust(maxidx, sz);
            }
        }
    }

    T gettop () {
        return data[1];
    }

    void settop (T val) {
        data[1] = val;
        adjust(1, size);
    }

    void show () {
        for (int i = 1; i <= size; i++) {
            std::cout << data[i] << "\t";
        }
        std::cout << std::endl;
    }
};

int main () {
    std::random_device rd;
    int data[5];
    for (int i = 0; i < 5; i++) {
        int cur = rd();
        data[i] = cur;
        //std::cout << cur << ",\t";
    }
    //std::cout << std::endl;

    Heap<int> hp(data, 5);
    std::cout << hp.gettop() << std::endl;
    for (int i = 0; i < 1000 * 1000 * 100; i++) {
        int peak = hp.gettop(), cur = rd();
        if (cur < peak) {
            hp.settop(cur);
        }
    }
    hp.show();
    return 0;
}


快速排序的:

#include <random>
#include <iostream>
#include <string>

template<class T> void swap (T *data, int i, int j) {
    T tmp = data[i];
    data[i] = data[j];
    data[j] = tmp;
}

template<class T> void topk_qsort (T *data, int start, int end, int k) {
    if (start < end) {
        int flagval = data[end];
        int i = start, j = start;
        for (; i < end; i++) {
            T cur = data[i];
            if (cur < flagval) {
                swap(data, i, j);
                ++j;
            }
        }

        swap(data, j, end);
        int len = j - start + 1;
        if (len > k) {
            topk_qsort(data, start, j - 1, k);
        } else if (len < k) {
            topk_qsort(data, j + 1, end, k - len);
        }
    }
}

int main (int argc, char *argv[]) {
    if (argc < 2) {
        std::cout << "input K" << std::endl;
        return 0;
    }
    int k = std::stoi(argv[1]);
    
    std::random_device rd;
    int *data = new int[20];
    for (int i = 0; i < 20; i++) {
        data[i] = rd() % 100;
    }
    for (int i = 0; i < 20; i++) {
        std::cout << data[i] << " ";
    }
    topk_qsort(data, 0, 20 - 1, k);
    std::cout << std::endl;
    for (int i = 0; i < k; i++) {
        std::cout << data[i] << " ";
    }
    std::cout << std::endl;
    delete []data;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值