topK问题之我见

题目:取出10000个数字中最大的500个,怎么做?

如果要解决100亿个数呢?这时数据不能全部装入内存,所以要尽可能少的遍历所有数据。

可以用容量为K的最小堆来存储最大的K个数。最小堆的堆顶元素就是最大K个数中最小的一个。每次新考虑一个数X,如果X比堆顶的元素Y小,则不需要改变原来的堆,因为这个元素比最大的K个数小。如果X比堆顶元素大,那么用X替换堆顶元素Y。在X替换堆顶元素Y之后,X可能破换最小堆的结构。(每个结点都比它的父亲结点大),需要更新堆来维持堆的性质。更新过程花费的时间复杂度为O(log2K)。

#include<stdio.h>
#define N 10001
#define K 5
void swap(int *a,int *b)
{
	*a = *a + *b;
	*b = *a - *b;
	*a = *a - *b;
}
void print(int *a,int n)
{
	int i;
	for(i=1;i<n;i++)
		printf("%8d",a[i]);
	printf("\n");
}
void min_heapadjust(int *a,int i,int size)
{
	int l = 2*i;
	int r = 2*i + 1;
	int min = i;
	if(l<=size && a[l]<a[i])
		min = l;
	if(r<=size && a[r]<a[min])
		min = r;
	if(min != i)
	{
		swap(&a[i],&a[min]);
		min_heapadjust(a,min,size);
	}
}
void build_heap(int *a,int size)
{
	int i;
	for(i=size/2;i>=1;i--)
		min_heapadjust(a,i,size);
}
void topK(int *a,int size)
{
	build_heap(a,size);
	int i;
	for(i=1;i<N;i++)
	{
		if(a[i]>a[1])
		{
			swap(&a[i],&a[1]);
			min_heapadjust(a,1,size);
		}
	}
}
int main(int argc,char *argv[])
{
	int a[N];
	int i;
	for(i=1;i<N;i++)
		a[i] = i;
	print(a,N);
	topK(a,K);
	print(a,K+1);
	return 0;
}


因此,算法只需要扫描所有的数据一次,时间复杂度为 O(N * log2K)。这实际上是部分执行了堆排序的算法。在空间方面,由于这个算法只扫描所有的数据一次,因此我们只需要存储一个容量为 K 的堆。大多数情况下,堆可以全部载入内存。如果 K 仍然很大,我们可以尝试先找最大的 K’个元素,然后找第 K’+1个到第 2 * K’个元素,如此类推(其中容量 K’的堆可以完全载入内存)。不过这样,我们需要扫描所有数据 ceil1(K/K’)次。


现在我介绍一种线性算法,但算法的范围会受到一定的限制。

如果所有 N 个数都是正整数,且它们的取值范围不太大,可以考虑申请空间,记录每个整数出现的次数,然后再从大到小取最大的 K 个。比如,所有整数都在(0, MAXN)区间中的话,利用一个数组 count[MAXN]来记录每个整数出现的个数(count[i]表示整数 i 在所有整数中出现的个数)。我们只需要扫描一遍就可以得到 count 数组。然后,寻找第 K 大的元素:

#include<stdio.h>
#define N 10000
#define K 5
#define MAX 20000 
void print(int *a,int n)
{
	int i;
	for(i=0;i<n;i++)
		printf("%8d",a[i]);
	printf("\n");
}
int count[MAX] = {0};
int topK(int *a,int max)
{
	int i;
	int sum = 0;
	for(i=0;i<N;i++)
		count[a[i]]++;
	for(i=max;i>=0;i--)
	{
		sum += count[i];
		if(sum >= K)
			break;
	}
	return i;
}
int main(int argc,char *argv[])
{
	int a[N];
	int i;
	for(i=0;i<N;i++)
		a[i] = i;
	print(a,N);
	i = topK(a,MAX);
	int m = MAX-1,j;
	for(j=0;;)
	{
		while(count[m]--)
		{
			printf("%8d",m);
			j++;
			if(j>=K)
				return 0;
		}
		m--;
	}
		
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值