100億個數中找出最大的前K個數(海量TopK問題)
阿新 • • 發佈:2019-02-08
對於這個問題,可以有以下思考:
給了多少記憶體儲存這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");
}
結果如下: