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


  《Programming Pearls》(編程珠璣)第一章講述了如何用位圖排序無重復的數據集,整個思想很簡潔,今天實踐了下。

一、主要思想

  位圖排序的思想就是在內存中申請一塊連續的空間作為位圖,初始時將位圖的每一位都置為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
10 
11 void setAllZero(unsigned char *p,long size);
12 void setOne(unsigned char *p,long loc);
13 int find(unsigned char *p,long loc);
14 bool getSorted(unsigned char *bitmap,char *fileName);
15 bool setBitmap(unsigned char *bitmap,char *fileName);
16 int bitmapSort();
17 int main(){
18     return bitmapSort();
19 }
20 int bitmapSort(){
21     unsigned char *bitmap;    //位圖指針
22     bitmap = (unsigned char *)malloc(BYTE_NUM*sizeof(unsigned char));
23     if(bitmap == NULL){
24         printf("Malloc failed\n");
25         return -1;
26     }    
27     setAllZero(bitmap,BYTE_NUM);//將位圖所有位設置為0
28     setBitmap(bitmap,"phoneNumber.txt");//掃描待排文件,將位圖對應位設置為1
29     getSorted(bitmap,"bitmapSort.txt");    //掃描位圖,將位圖為1的位號輸出到文件
30     free(bitmap);//釋放位圖
31     return 0;
32 }
33 /***********設置待排序數據的位圖**************/
34 bool setBitmap(unsigned char *bitmap,char *fileName){
35     FILE *readFp;
36     printf("Setting bitmap...\n");
37     readFp = fopen(fileName,"r");
38     if(readFp == NULL)
39         return false;    
40     long phoneNum=0;
41     while(fscanf(readFp,"%ld\n",&phoneNum) != EOF){
42         setOne(bitmap,phoneNum);//將    phoneNum位設置為1    
43     }
44     fclose(readFp);
45     return true;
46 }
47 /*****順序遍歷位圖輸出記錄,從而實現排序****************/
48 bool getSorted(unsigned char *bitmap,char *fileName){
49     printf("Search bitmap...\n");
50     FILE *writeFp;
51     writeFp = fopen(fileName,"w");
52     if(writeFp == NULL)
53         return false;
54     long phoneNum=0;
55     for(phoneNum = 0; phoneNum < MAX_NUM; phoneNum += 1){
56         if(find(bitmap,phoneNum)){
57             fprintf(writeFp,"%ld\n",phoneNum);
58         }
59     }
60     fclose(writeFp);
61     return true;
62 }
63 /******先將位圖清零********/
64 void setAllZero(unsigned char *bitmap,long size){
65     for(long i=0;i<size;i++)
66         *(bitmap+i) &= 0;
67 }
68 /*************************************************
69 將指定的位置為1
70 (loc>>3)相當於整除2^3=8,即定位到字節數,MASK=0x07,loc&MASK相當於loc%8
71 ***************************************************/
72 void setOne(unsigned char *bitmap,long loc){
73     *(bitmap+(loc>>3)) |= (1<<(loc&MASK));//
74 }
75 
76 /******查找指定的位是否為1********/
77 int find(unsigned char *bitmap,long loc){
78     return ((*(bitmap+(loc>>3))) & (1<<(loc&MASK))) == (1<<(loc&MASK));    
79 }

 

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

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

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

  完整的代碼可以參考github

  感謝關注,歡迎評論。

  轉載請注明出處:http://www.cnblogs.com/fengfenggirl/

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM