《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/