欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

100亿个数中找出最大的前K个数(海量TopK问题)

程序员文章站 2022-03-13 12:33:47
...

对于这个问题,可以有以下思考:
给了多少内存存储这100亿个数据?
先思考:堆排序效率是nlogn,再思考:可以将这些数据切成等份,再从每一份中找出最大前k个数据,但是效率不高。那如果利用堆的性质呢?
小堆堆顶元素最小,先将前k个数建成小堆,那么堆顶元素就是最小的,k+1的数依次与堆顶元素进行比较,如果大于,则K+1与堆顶元素交换,依次循环直至所有数据全部比较完,那么这个堆里存放的是最大的前K 个数。
代码如下:

void HeapInit(Heap* hp, HeapDatatype* a, int n)//堆的初始化
{
    assert(a&&hp);
    hp->capacity = n;
    hp->size = n;
    hp->arry = (HeapDatatype*)malloc(sizeof(HeapDatatype)*hp->capacity);
    int i = 0;
    for (i = 0; i < hp->size; i++)
        hp->arry[i] = a[i];
}
void SmallHeapMake(Heap* hp,int k)//小堆的创建
{
    int i = 0;//从大堆最后一个非叶子结点开始
    for (i = (k - 1 - 1) / 2; i >= 0; i--)
    {
        SmallHeapAdjustDown(hp, i,k);
    }
}
void SmallHeapAdjustDown(Heap*hp, int parent, int k)//向下调成小堆
{
    int child = parent * 2 + 1;
    while (parent < k)
    {    //如果左孩子大于右孩子,将child等于右孩子,即child存左右孩子较小坐标
        if (child + 1 < hp->size && hp->arry[child] > hp->arry[child + 1])
            child = child + 1;
        if (child < k &&hp->arry[parent] > hp->arry[child])//根大于孩子
        {
            Swap(&hp->arry[parent], &hp->arry[child]);
            // 调整之后需要以child为跟再调整一次,因为调整后可能导致根小于左右孩子
            parent = child;
            child = parent * 2 + 1;
        }
        else  //说明根大于左右孩子或者child大于hp->size,退出循环
            break;
    }
}
void HeapFindTopK(Heap *hp, int k, int n)//找一组数据里最大的前K个数,利用堆
{
    SmallHeapMake(hp,k);//建成小堆
    for (int j = k; j < n; j++)
    {
        if (hp->arry[0] < hp->arry[j])
            Swap(&hp->arry[0], &hp->arry[j]);
        SmallHeapAdjustDown(hp,  0,k);//小堆向下调整
    }
}

如果不建堆,直接用数组存K个数呢?
代码如下:

void AdjustDown(int *a, int k, int root)//k个数据向下调整,没有建堆
{
    int parent = root;
    int child = parent * 2 + 1;
    if ((child+1)<k&&a[child] >a[child + 1])
        child++;
    while (child < k)
    {
        if (a[parent] >a[child])
        {
            Swap(&a[parent], &a[child]);
            parent = child;
            child = parent * 2 + 1;
        }
        else
            break;
    }
}
void FindTopK(int *a, int k, int n)//找一组数据里最大的前K个数
{    //没有建堆
    //先将前k个数调整成a[0]是最小数,k+1的数与a[0]元素进行比较,
    //如果大于,则交换,再将这K个数调整为a[0]最小,循环直至全部比较
    //那么数组里前k个数是最大的前k个数
    int i = 0;
    for (i = (k - 1 - 1) / 2; i >= 0; i--)
    {
        AdjustDown(a, k, i);//调整为a[0]最小
    }
    for (int j = k; j <n; j++)
    {
        if (a[j] > a[0])
            Swap(&a[j], &a[0]);
        AdjustDown(a, k, 0);
    }
}

Test函数:

void TestFindTopK()
{
    int a[] = {44 ,2 ,71 ,8,12,67,89};
    int i = 0;
    int k = 3;
    Heap hp;
    int size = sizeof(a) / sizeof(int);
    HeapInit(&hp, a, size);
    HeapFindTopK(&hp, k, size);
    for (i = 0; i < k; i++)
    {
        printf("%d ", hp.arry[i]);
    }
    printf("\n");
    FindTopK(a, k, size);//找出最大的前K个数
    for (i = 0; i < k; i++)
        printf("%d ", a[i]);
    printf("\n");
}

结果如下:
100亿个数中找出最大的前K个数(海量TopK问题)

相关标签: 堆的应用

上一篇: 碰撞检测

下一篇: 碰撞检测