2.5亿个整数中找出不重复的数代码实现

对于在2.5亿个整数中找出不重复的数(内存不足以容纳这2.5亿个整数),已经有众多前辈给出了比较详尽的算法描述,但在具体实现上现有的例子则比较简单,规模偏小。昨天生成2.5亿个数之后,开始读文件统计,结果10个小时左右没出结果。所以今天用25万个数做实验,几分钟就得出了统计结果。在VS2010环境里新建C++控制台工程,完整的实现代码如下。程序算法实现部分参考http://blog.csdn.net/leeboy_wang/article/details/8980682,在此向转载者和原作者表示衷心谢意。

//题目要求:在2.5亿个整数中找出不重复的整数,内存不足以容纳这2.5亿个整数。
//采用2-Bitmap方法,2bit标识一个数字状态,00表示未出现过,01表示出现1次,10表出现两次或更多
#include"StdAfx.h"
#include<stdlib.h>
#include<string.h>
#include<memory.h>


//2.5亿个整数所需存储空间为2.5*10^8*4=10^9 Byte=976562.5KB=953.6743MB<1G
//num=rand();用来生成随机数,每次都生成一个介于0-RAND_MAX范围内的数。
//RAND_MAX=32767


//用char数组存储2-Bitmap,不用考虑大小端内存的问题
//2^15=32768,用两个bit标识一个随机数的状态,共需要2^16bit=2^13Bytes=8192Byte
//char数组的大小不低于8192,这里定义为8200
unsigned char flags[8200]; //数组大小自定义

//获取数字出现的次数,参数为要查看的数字,首先计算该数字的两个标识位所在的位置,由i,j确定
unsigned get_val(int idx)
{
    int i = idx/4;
    int j = idx%4;
    unsigned ret = (flags[i]&(0x3<<(2*j)))>>(2*j);
    return ret;
}

//标识位的置位函数,参数为遍历时对应的数字idx,val对应的是该数字的出现次数,第一次出现就为1
unsigned set_val(int idx, unsigned int val)
{
    int i = idx/4;
    int j = idx%4;
    unsigned tmp = (flags[i]&~((0x3<<(2*j))&0xff)) | (((val%4)<<(2*j))&0xff);
    flags[i] = tmp;
    return 0;
}

//对遍历到的每个数字要执行的操作
//首先根据数字值获得该数字出现的次数,如果>=2次就不执行任何操作,返回。
//如果数字出现过0次或1次,就在该数字对应的标识位上加1.
unsigned add_one(int idx)
{
    if (get_val(idx)>=2) {
        return 1;
    }
    else  {
        set_val(idx, get_val(idx)+1);
        return 0;
    }
}

//生成2.5亿个随机数的函数
void CreateNumFiles()
{
	int i,j,k;//标识三重循环,生成2.5亿个数
	int randnumber;//存储生成的随机数
	FILE *fp;
	char str[10];//存储序号,用于生成文本文件的命名
	char ext[5]=".txt";//存储文本文件的扩展名
	for(i=0;i<2500;i++)
	{
		sprintf(str,"%d",i+1);//数字转字符串的函数,最后结果存储到字符串str里
		strcat(str,ext);//字符串拼接,用于生成最终的文件名,如"1.txt""2.txt"
		fp=fopen(str,"w+");//创建文件,准备写入数字
		char randnum[10];//存储转换成字符串后的数字,用于拼接换行符后写入文件,直接写入数字会造成各数字间无间距,不能区分。
		for(j=0;j<100;j++)
		{
			for(k=0;k<1000;k++)
			{
				randnumber=rand();//生成随机数
				sprintf(randnum,"%d",randnumber);//随机数转成字符串
				strcat(randnum,"\n");//拼接换行符
				fprintf(fp,randnum);//完成写入一行的操作
			}
		}
		fclose(fp);//写完一个文件后,关闭文件指针
	}
}

//生成25万个随机数的函数,与上个函数一样,2.5亿个数的运行时间过长,本次测试将规模缩小到25万个随机数
void CreateNumFiles_Less()
{
	int i,j,k;
	int randnumber;
	FILE *fp;
	char str[10];
	char ext[5]=".txt";
	for(i=0;i<25;i++)
	{
		sprintf(str,"%d",i+1);
		strcat(str,ext);
		fp=fopen(str,"w+");
		char randnum[10];
		for(j=0;j<100;j++)
		{
			for(k=0;k<100;k++)
			{
				randnumber=rand();
				sprintf(randnum,"%d",randnumber);
				strcat(randnum,"\n");
				fprintf(fp,randnum);
			}
		}
		fclose(fp);
	}
}

//只测试非负数的情况;
//假如考虑负数的话,需增加一个2-Bitmap数组.
//int a[]={1, 3, 5, 7, 9, 1, 3, 5, 7, 1, 3, 5,1, 3, 1,10,2,4,6,8,0};网上程序自带的测试数组,有兴趣的可以自己先测测

//使用25万个数测试,运行时间不超5分钟,运行结果记录到Results的文本文件中。
int main()
{
    int i;
    memset(flags, 0, sizeof(flags));
    
    /*printf("原数组为:");
    for(i=0;i < sizeof(a)/sizeof(int); ++i)  {
        printf("%d  ", a[i]);
        add_one(a[i]);
    }
    printf("\r\n");*/

	//FILE *fp_read;
	int filecount=0;//标识文件序号
	
	FILE *fp_read;//统计操作时用于打开存数字的文本文件
	char str[10];//用于存储数字,作为文件名的前半部分
	char ext[5]=".txt";//扩展名txt,用于拼接字符串生成相应的文件名
	char buf[10];//缓冲区,用于读取数字文件时存储读到的数字
	int tempint;//对于文本里读出的每个数,tempint用于存储该数字
	
	
	//遍历25个文件,首先拼接文件名,再读取每行字符,转成整数后进行置位操作
	for(filecount=0;filecount<25;filecount++)
	{
		sprintf(str,"%d",filecount+1);
		strcat(str,ext);
		fp_read=fopen(str,"r");
		if(filecount==0)
		{
			
			if(fp_read==NULL)
				CreateNumFiles_Less();
		}
		
		while(!feof(fp_read))
		{
			fgets(buf,10,fp_read);
			tempint=atoi(buf);
			//printf("%d\n",tempint);
			add_one(tempint);
		}
		fclose(fp_read);
		printf("第%d个文件读取完成!\n",filecount+1);
	}
	
	//首先新建文本文件,再遍历标识数组,把出现一次的数字写入到Result.txt文件中
	FILE *fp_result;
	fp_result=fopen("Result.txt","w+");
    fprintf(fp_result,"只出现过一次的数:");
    for(i=0;i < 32768; ++i)  {
        if(get_val(i) == 1)
            fprintf(fp_result,"%d\n",i);
    }
	fclose(fp_result);
    printf("执行完毕\n");

    return 0;
}
点赞