给10^7个无重复的整数排序
题目:给10^7个无重复的整数排序,即1到10000000,尽量使空间复杂度小。
答:首先,我们利用程序来随机生成这10^7个整数,用一个全局数组存储着10^7个整数,然后根据随机生成的下标交换其中两个整数的位置,以达到我们所说的整数是随机的,具体请看下面的代码:
const int N = 10000000; int data[N] = {0}; void swap(int &a, int &b) { int temp = a; a = b; b = temp; } void generate_no_repeat_number(void) { FILE *fp = fopen("unsort_data.txt", "w"); assert(fp != NULL); for (int i = 0; i < N; i++) //先按整数顺序初始化 { data[i] = i + 1; } int p ,q; srand((unsigned)time(NULL)); for (int i = 0; i < N; i++) //通过随机交换下标的方法,来使者10^7个整数达到真正的随机 { p = (rand() * RAND_MAX + rand()) % N; q = (rand() * RAND_MAX + rand()) % N; swap(data[p], data[q]); } for (int i = 0; i < N; i++) { fprintf(fp, "%d ", data[i]); } fclose(fp); }
对于无重复的整数排序,用位图法是最合适不过了,下面我们就来看看如何用位图来实现对这10^7个整数排序。
第一种方法,我们可以借助一个数组,比如数组的第一个元素就表示0-31,第二个元素就表示32-63。这样一个整数就用一个bit来表示。请看下面的代码:
int flag[N / 32 + 1] = {0}; //函数堆栈默认1M,此数组大小大约为1.2M,已然大于默认堆栈大小,故用全局数组表示 void sort_by_array(void) { clock_t begin = clock(); FILE *fpread = NULL; FILE *fpwrite = NULL; fpread = fopen("unsort_data.txt", "r"); fpwrite = fopen("sort_by_array.txt", "w"); assert(fpread != NULL); assert(fpwrite != NULL); int num; while (EOF != fscanf(fpread, "%d", &num)) //从未排序的文件中读入数据 { flag[num / 32] |= (1 << (num % 32)); //将num对应数组中的某个元素的1位置为1 } for (int i = 1; i <= N; i++) { if ((flag[i/32] >> (i % 32)) & 1) //从数组开始对每一个元素的每一位测试,若为1则输出该数 { fprintf(fpwrite, "%d ", i); } } clock_t end = clock(); cout<<"借助数组排序所需时间为: "<<(end - begin)/CLK_TCK << "s"<<endl; fclose(fpread); fclose(fpwrite); }
第二种方法,直接借助STL中的bitset,这样就更加简单了,代码如下:
bitset<N + 1> bit_map; void sort_by_bitset(void) { clock_t begin = clock(); FILE *fpread = NULL; FILE *fpwrite = NULL; fpread = fopen("unsort_data.txt", "r"); fpwrite = fopen("sort_by_bitset.txt", "w"); assert(fpread != NULL); assert(fpwrite != NULL); int num; bit_map.reset(); //全部清零 while (EOF != fscanf(fpread, "%d", &num)) { bit_map.set(num, 1); //将该数对应的位 置为1 } for (int i = 1; i <= N; i++) { if (bit_map[i] == 1) //若该数对应的位等于1,则输出该数 { fprintf(fpwrite, "%d ", i); } } clock_t end = clock(); cout<<"借助bitset排序所需时间为: "<<(end - begin)/CLK_TCK << "s"<<endl; fclose(fpread); fclose(fpwrite); }
我们首先来看,未排序的数字如unsort_data.txt所示:
通过上面两种方法,对文件排序如下图所示,其中sort_by_array.txt借助于数组,sort_by_bitset.txt借助于bitset。
上面的这两种方法,需要的内存大约都为1.2M,如果我只有1M不到的内存,那又该如何对这10^7个整数排序呢?其实不难,可以采用分治法,比如:先对1到5000000的整数排序,再对5000000到10000000的整数排序,请看下面代码,我只给出用数组去实现排序,内存使用大约为0.6M,如果空间复杂度要求更小,那可以接着对这些整数进行分治。STL的bitset就不写了,very easy。。。
const int MAX_PART = 5000000; void sort_by_part_array(void) { clock_t begin = clock(); FILE *fpread = NULL; FILE *fpwrite = NULL; fpread = fopen("unsort_data.txt", "r"); fpwrite = fopen("sort_by_part_array.txt", "w"); assert(fpread != NULL); assert(fpwrite != NULL); int flag_part[MAX_PART / 32 + 1] = {0}; int num; while (EOF != fscanf(fpread, "%d", &num)) { if (num <= MAX_PART) { flag_part[num / 32] |= (1 << (num % 32)); } } for (int i = 1; i <= MAX_PART; i++) { if ((flag_part[i/32] >> (i % 32)) & 1) { fprintf(fpwrite, "%d ", i); } } fseek(fpread, 0, SEEK_SET); memset(flag_part, 0, MAX_PART/32 + 1); while (EOF != fscanf(fpread, "%d", &num)) { if (num > MAX_PART) { num -= MAX_PART; flag_part[num / 32] |= (1 << (num % 32)); } } for (int i = 1; i <= MAX_PART; i++) { if ((flag_part[i/32] >> (i % 32)) & 1) { fprintf(fpwrite, "%d ", i + MAX_PART); } } clock_t end = clock(); cout<<"借助部分数组排序所需时间为: "<<(end - begin)/CLK_TCK << "s"<<endl; fclose(fpread); fclose(fpwrite); }
下图为sort_by_part_array.txt与sort_by_array.txt的文件对比
扩展:
腾讯笔试题:判断数字是否出现在40亿个数中?给40亿个不重复的unsigned int的整数,没排过序的,然后再给几个数,如何快速判断这几个数是否在那40亿个数当中?
如前所述用位图法,unsigned int 的取值范围是0到2^32-1。我们可以申请连续的2^32/8=512M的内存,用每一个bit对应一个unsigned int数字。首先将512M内存都初始化为0,然后每处理一个数字就将其对应的bit设置为1。当需要查询时,直接找到对应bit,看其值是0还是1即可。
2013年1月22日 venow 完