1. 程式人生 > >100億個數中找出最大的前K個數(海量TopK問題)

100億個數中找出最大的前K個數(海量TopK問題)

對於這個問題,可以有以下思考:
給了多少記憶體儲存這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");
}

結果如下:
這裡寫圖片描述