瘋狂位圖之——位圖實現12GB無重複大整數集排序

一、主要思想

  位圖排序的思想就是在內存中申請一塊連續的空間作爲位圖,初始時將位圖的每一位都置爲0,然後依次讀取待排序文件的整數,將整數所在的位設置爲1,最後掃描位圖,如果某一位爲1,則說明這個數存在,輸出到已排序文件。比如待排序的數據S={3,0,4,1,7,2,5},max(S)=7,我們可以設置一個八位的位圖B,將位圖的每一位初始爲0,即B=[0,0,0,0,0,0,0,0],對S中的每一個整數d,設置B[d]=1,即B=[1,1,1,1,1,1,0,1],最後掃描位圖,對位圖的每一位i,如果B[i]==1,則輸出i到已排序文件,排序後的S={0,1,2,3,4,5,7}。

  整個過程只需要遍歷一遍待排序文件和位圖,時間複雜度O(n),需要的輔助空間爲(max(S)/8)B。雖然這個排序算法只能在無重複的整數集上運行,但對於有些需求,確實做到高效實現,比如說給手機號碼排序,手機號碼11位,第一位始終爲1,理論上可以有10^10個號碼,但一些號碼未發放,即有些號碼在系統中不存在,假設系統中有50%的合法號碼,每個號碼用long int表示,這麼多號碼所需要的空間爲50%*(10^10)*4B=20GB,不能放在內存中進行快速排序。一個可選的方案是分多趟進行歸併排序,但需要較長的時間。我們申請一個10^10位的位圖,需要的內存是10^10/8B=1.25GB,完全可以在當代的PC機上運行,在掃描位圖時,假設某一位i爲1,輸出文件時,在前面添加一個1,例如i=3885201314,輸出爲13885201314。

二、算法實現

   用c語言實現的話,需要自己封裝位圖操作,這裏需要用到三個操作:設置位圖的所有位爲0(setAllZero);設定指定的位爲1(setOne);查看指定的位是否爲1(find);代碼如下:

複製代碼
 1 #include <malloc.h>
 2 #include <stdlib.h>
 3 #include <stdio.h>
 4 #include <time.h>
 5 #include <math.h>
 6 7#define MAX_NUM 16777216//最大的數,也就是需要的位 8#define BYTE_NUM (1+MAX_NUM/8)//字節數 9#define MASK 0x07
1011void setAllZero(unsigned char *p,long size);12void setOne(unsigned char *p,long loc);13int find(unsigned char *p,long loc);14bool getSorted(unsigned char *bitmap,char *fileName);15bool setBitmap(unsigned char *bitmap,char *fileName);16int bitmapSort();17int main(){18return bitmapSort();19}20int bitmapSort(){21     unsigned char *bitmap;    //位圖指針22     bitmap = (unsigned char *)malloc(BYTE_NUM*sizeof(unsigned char));23if(bitmap == NULL){24         printf("Malloc failed\n");25return -1;26    }    27     setAllZero(bitmap,BYTE_NUM);//將位圖所有位設置爲028     setBitmap(bitmap,"phoneNumber.txt");//掃描待排文件,將位圖對應位設置爲129     getSorted(bitmap,"bitmapSort.txt");    //掃描位圖,將位圖爲1的位號輸出到文件30     free(bitmap);//釋放位圖31return0;32}33/***********設置待排序數據的位圖**************/34bool setBitmap(unsigned char *bitmap,char *fileName){35     FILE *readFp;36     printf("Setting bitmap...\n");37     readFp = fopen(fileName,"r");38if(readFp == NULL)39returnfalse;    40long phoneNum=0;41while(fscanf(readFp,"%ld\n",&phoneNum) != EOF){42         setOne(bitmap,phoneNum);//將    phoneNum位設置爲1    43    }44    fclose(readFp);45returntrue;46}47/*****順序遍歷位圖輸出記錄,從而實現排序****************/48bool getSorted(unsigned char *bitmap,char *fileName){49     printf("Search bitmap...\n");50     FILE *writeFp;51     writeFp = fopen(fileName,"w");52if(writeFp == NULL)53returnfalse;54long phoneNum=0;55for(phoneNum = 0; phoneNum < MAX_NUM; phoneNum += 1){56if(find(bitmap,phoneNum)){57             fprintf(writeFp,"%ld\n",phoneNum);58        }59    }60    fclose(writeFp);61returntrue;62}63/******先將位圖清零********/64void setAllZero(unsigned char *bitmap,long size){65for(long i=0;i<size;i++)66         *(bitmap+i) &= 0;67}68/*************************************************69將指定的位置爲170(loc>>3)相當於整除2^3=8,即定位到字節數,MASK=0x07,loc&MASK相當於loc%871***************************************************/72void setOne(unsigned char *bitmap,long loc){73     *(bitmap+(loc>>3)) |= (1<<(loc&MASK));//74}7576/******查找指定的位是否爲1********/77int find(unsigned char *bitmap,long loc){78return ((*(bitmap+(loc>>3))) & (1<<(loc&MASK))) == (1<<(loc&MASK));    79 }
複製代碼

 

  C++的STL中有一個數據結構bitset,操作位圖很方便。  

複製代碼
 1 #include <bitset>
 2#define MAX_NUM 4000000//最多的數,即需要的位數 3usingnamespace std; 4 5int main(){ 6     FILE *readFp,*writeFp; 7     readFp = fopen("phoneNumber1.txt","r");         8     writeFp = fopen("bitsetSorted.txt","w");     9     bitset<MAX_NUM> bitmap;10for(long i=0;i<MAX_NUM;i++){//先將位圖初試化爲011         bitmap.set(i,0);12    }13     printf("Begin set bitmap...\n");14long number = 0;15while(fscanf(readFp,"%ld\n",&number) != EOF){16         bitmap.set(number,1);//將number所在位設置爲1        17    }18     printf("Begin search bitmap...\n");19for(long i=0;i<MAX_NUM;i++){20if(bitmap[i] == 1)//將位1的位輸出到已排序文件21             fprintf(writeFp,"%ld\n",number);22    }23    fclose(writeFp);24    fclose(readFp);25 }
複製代碼

  排序算法很快就寫好了,就開始生成測試數據,想生成0—2^31的亂序數據集還真不容易,首先要保證不重複,第二要丟掉40%的數(無效手機號碼),第三要儘可能的亂序,搗了很久,最終還是找到了實現辦法,生成了12GB的數據集,關於生成這個數據集的辦法,歡迎一起討論,我將會在下一篇中總結一下我的方法。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章