现在的位置: 首页 > 综合 > 正文

海量数据处理之求1亿个整数中的最大的k个数

2013年10月11日 ⁄ 综合 ⁄ 共 3202字 ⁄ 字号 评论关闭

题目描述:

输入:一亿个整数,有重复的数字,整数保存在一个文件中

输出:文件中最大的k个数

限制:尽量以最快的速度完成任务。

具体解决方法:

1. 位图解决

位图为用比特位来存储数据,如果i比特位为1,则该位在表示整数i,为0,则不是

用该方法主要提供三个函数接口:设置比特位:set_bit(int *data, int  num)  

清除比特位:clr_bit(int *data, int num)

获得某个比特位:get_bit(int *data, int num)

这些函数具体实现都用位操作实现

具体实现代码:

#include <iostream>
#include <cstdio>
#include <sys/time.h>
#include <cstdlib>
using namespace std;
#define BITSIZE 32
#define SHIFT 5
#define SIZE 10000000
void set_bit(int *data, int num)
{
	data[num>>SHIFT] |= 0x01<<(num&(BITSIZE-1)); 
}

void clr_bit(int *data, int num)
{
	data[num>>SHIFT] &= ~(0x01<<(num&(BITSIZE-1)));
}

int get_bit(int *data, int num)
{
	return (data[num>>SHIFT] & (0x01<<(num&(BITSIZE-1))));
}

int main(void)
{
	FILE *fp;
	int k=0;
	fp=fopen("10_million_data.txt", "r");
	struct timeval start, end;
	int a[312500]={0};
	int num=0;
	gettimeofday(&start, NULL);
	while(!feof(fp))
	{
		fscanf(fp, "%d", &num);
		if(feof(fp))		break;
		set_bit(a, num);
	}
	for(long long int i=SIZE; i>=1; i--)
	{
		if(get_bit(a, i))
		{
			++k;
			if(k>10)	break;
			cout << i << " ";
		}
	}
	cout << endl;
	gettimeofday(&end, NULL);
	float t_time = end.tv_sec-start.tv_sec+(end.tv_usec-start.tv_usec)/1000000.0;
	cout << "time used : " << t_time << endl;
	return 0;
}

2. 用标准库关联性容器set实现

说明:set中的元素是有序的,默认是升序的,且数组元素不重复

边插入边排序,具体代码如下:

int main(void)
{
	set<int> s;
	FILE *fp;
	int num=0;
	fp=fopen("10_million_data.txt", "r");
	struct timeval start, end;
	gettimeofday(&start, NULL);  //linux系统调用函数
	while(!feof(fp))
	{
		fscanf(fp, "%d", &num);
		s.insert(num);
	}
	gettimeofday(&end, NULL);
	float t_time = end.tv_sec-start.tv_sec+(end.tv_usec-start.tv_usec)/1000000.0;
	cout << t_time << endl;
	//set<int>::iterator it=s.begin();
	//for(; it!=s.end(); ++it)
	
	return 0;
}

3.  用10个小根堆实现查找较大的10个数,时间复杂度为O(10000000*logk)

思路:先用文件中的前十个数初始化为一个10个元素的小根堆,默认为较大的10个数,再逐个从文件中读文件,比根顶元素大就调整小根堆,知道文件读完为止

具体实现代码:

void exch(long int &a, long int &b) {	long int temp=a; a=b; b=temp;	}

void sink(long int *data, int pos)
		int l=2*pos;
		if(l<data[0] && data[l]>data[l+1]) l++;
		if(data[l]>data[pos]) break;
		exch(data[l], data[pos]);
		pos=l;
	}
}

void swim(long int *data, int pos)
{
	while(pos>1 && data[pos/2]<data[pos])	
	{
		exch(data[pos/2], data[pos]);
		pos=pos/2;
	}
}

void select_topk(FILE *pf, long int *data)
{
	int i=0, j=10;
	long int num=0;
	for(i=k/2; i>=1; i--)
		sink(data, i);
	while(!feof(pf))
	{
		fscanf(pf, "%ld", &num);
		if(feof(pf))	break;
		j++;
		if(num>data[1])
		{
			exch(data[1], num);
			sink(data, 1);
		}
	}
	cout << j << endl;
}

int main(void)
{
	long int topk[k+1]={k};
	struct timeval start, end;
	long int num=1;
	FILE *pf;
	//pf=fopen("3milliondata.txt", "r");
	pf=fopen("10_million_data.txt", "r");
	gettimeofday(&start, NULL);
	
	for(long int i=1; i<=10; i++)
	{
		fscanf(pf, "%ld", &topk[i]);
	}
    select_topk(pf, topk);	

    gettimeofday(&end, NULL);
	float time=end.tv_sec-start.tv_sec+(end.tv_usec-start.tv_usec)/1000000.0;
	printf("time is used %f\n", time);
	for(int i=1; i<=k; i++)
		cout << topk[i] << " ";
	cout << endl;
	return 0;
}

三中方案的运行时间比较,其中第三种方法输出带有重复的元素

另外在小数据时寻找mink或者maxk,可以考虑使用快速排序的思想来实现,具体代码如下

void exch(int &a, int &b)
{
	int temp=a;
	a=b;
	b=temp;
}

int Less(int a, int b) { return (a<b ? 1 : 0); }

int randint(int min, int max)	
{
	return (rand()%(max-min+1)+min); 	
}

void qsort_mink(int *data, int l, int h, int k)
{
	int i=l, j=h+1;
	exch(data[l], data[randint(l+1, h)]);
	int key = data[l];
	while(1)
	{
		while(Less(data[++i], key))	if(i==h)	break;
		while(!Less(data[--j], key)) if(j==l)	break;
		if(i>j)	break;
		exch(data[i], data[j]);
	}
	exch(data[l], data[j]);
	if(j>k)
		qsort_mink(data, l, j-1, k);	//在j的左半边寻找
	if(j<k)
		qsort_mink(data, j+1, h, k);		//在j的右半边寻找
}

该方法是时间复杂度为O(nlogn)

总结:

从运行结果中看出:小根堆排序实现 < 位图实现 < C++STL实现

所以topk问题一般用小根堆或大根堆实现,或者可以使用位图(当然数字一大时就会出现内存不够的现象)

抱歉!评论已关闭.