天天看點

位圖排序思想及代碼詳解

輸入:一個最多包含n個正整數的檔案,每個數都小于n,其中n=10^7。如果在輸入檔案中有任何重複整數出現就是緻命錯誤。沒有其他資料與該整數相關聯

輸出:按升序排列的 輸入整數的清單。

限制:最多有(大約)1MB的記憶體空間可用,有充足的磁盤存儲空間可用。運作時間最多幾分鐘,運作時間為10秒就不需要進一步優化了。

    從0開始解決這個問題的話,可以把它分為兩個部分:首先随機生成K個小于N的數;再對這K個數排列。

    系統參數:GCC:#pragma pack(4)    小端模式 -std=c99

一、位圖思想(bit-map)

    引入:如何用一個Byte表示盡可能多的無重複的數?

    一個bit隻能表示0或1,兩個bit能表示2(01)和3(11),4則要3個bit,這樣的話8個bit隻能表示0-3四個數字。

    我們得換個思路,暫且先丢掉頭腦中的進制,用國小生排隊的思想,拍第一的報1,第二的報2…嗯哼,我們一位元組8個bit,這樣就表示了8個數字。

    是以,讓我們走進記憶體中,丢掉進制的羁絆,來數數這個bit排第幾。

位圖排序思想及代碼詳解

    這張圖以左邊為低位的話,就和作者系統的小端模式契合

二、生成K個小于N的不重複随機數

    基于位圖的思想,我們可以用K個bit來表示這麼多個數字。由于存在記憶體對齊等問題,我聲明了一個32Bit的union結構,為了更好的對這32個bit進行操作,我把它分為了4個char,而兩個short則是為了測試與debug。

  1. typedef union str{  
  2.         short element[2];  
  3.         char ele_ch[4];  
  4.     }Bits;  
  5.     int n = 0;  
  6.     printf("Please input range:\n");  
  7.     scanf("%d",&n);   
  8.     const int N = n/32+1;  
  9.     Bits arr[N];  
  10.     memset(arr,0,sizeof(arr));  
  11.     int k = 0;  
  12.     printf("Please input count of number,");  
  13.     do{  
  14.         printf("this number is less than %d\n",n);  
  15.         scanf("%d",&k);  
  16.     }while(k>n);  

是以,當生成了一個随機數num後,就把記憶體中第num位置為1,以表示該數已存在,同理,檢查是否有重複時,隻需看該位的值。

對檢查該位的值以及修改該位的值這兩個操作,用兩個函數表示。

  1. /*      檢視該位是否為0        * 
  2. *       輸入:一個整數n        * 
  3. *       傳回:bit[n]的值         */  
  4. bool bitValue(int num,Bits arr[],...);  
  5. /*      把某位設為n                  * 
  6. *       輸入:一個整數n代表的位址       * 
  7. *       操作:将該位址上的值設為1       */  
  8. void setBitValue(Bits arr[],...); 

是以我們可以用這樣一個循環來尋找随機數。用rand得到随機數後,再判斷這個數是否出現過,如果沒出現過的話就把那一位設為1,并輸出。

  1. while(k)  
  2. {  
  3.     int value = rand()%n;  
  4.     if(bitValue(value,arr)==0)  
  5.     {  
  6.         setBitValue(arr,n);  
  7.         printf(" %d",value);  
  8.         k--;  
  9.     }  
  10. }  

由于申請的記憶體是以4B(32bit)為一個機關,是以N個數字在記憶體中有如下x種情況:

        1.N為32 的倍數,則申請到的數組全滿。(N = 32*n)

        2.除最後一個數組外,其餘數組全滿。也分兩種情況:

            ①.最後一個數組中,前x個char全滿,後一個char被用到的bit小于8個。(N = n*32+8*x+bit)

            ②. 最後一個數組中,恰好滿了x個char。(N = n*32+8*x)

    是以bool bitValue(int num ,Bits arr[])接受兩個參數,一個是數組首位址,一個是待求的數字(0~n-1)

  1. bool bitValue(int num,Bits arr[])  
  2. {     
  3.     if(num!=0)  
  4.         int arr_n = num/(32);   //在第幾個數組   
  5.         int arr_b = num-arr_n*32;  
  6.         short arr_ch = 0;   //在第幾個char裡   
  7.         if(arr_b)  
  8.         {  
  9.             arr_ch = arr_b/8;  
  10.         }  
  11.         int arr_c_b = arr_b;    //第幾個bit   
  12.         while(arr_c_b>=8)  
  13.             arr_c_b -= 8;  
  14.         return (arr[arr_n].ele_ch[arr_ch] & arr2n[arr_c_b]);  
  15.     else    //處理0   
  16.         return (arr[num].ele_ch[0] & arr2n[0]);   
  17. }

    又因為是以8bit為一機關,是以建立數組全局數組 arr2n[8],用來存儲2^0~2^7的值。在小端模式下,恰好是左第一位為2^0。

  18. const int arr2n[8]={1,2,4,8,16,32,64,128};  

    是以void setBitValue()也和bool bitValue()過程類似,找到n在記憶體中的位置,在更新它的值。不過在這個函數裡重複計算位置不劃算,是以我們可以在第一次計算時把位置資訊儲存起來,要更新資料時再傳給函數。

    這裡申明一個位置結構,用來存儲一個數字在記憶體中相對于數組結構的位置。

  19. typedef struct bitLoca{  
  20.     int n_arr;  
  21.     int n_ch;  
  22.     int n_bit;  
  23. }bitLoca;  

然後在在主函數的while循環裡加上它,并修改兩個函數的參數:

  1. int main(void)  
  2.     //...  
  3.     while(key)  
  4.             bitLoca bitL;  
  5.             int value = rand()%n;  
  6.             if(bitValue(value,arr,&bitL)==0)  
  7.             {  
  8.                 setBitValue(arr,&bitL);  
  9.                 printf("%d\n",value);  
  10.                 key--;  
  11.             }  
  12.     //...   
  13. 更新後的bitValue():
  14. bool bitValue(int num,Bits arr[],bitLoca *bitL)  
  15.         int arr_n = num/(32);  
  16.         int arr_c_b = arr_b;  
  17.         bitL->n_arr = arr_n;  
  18.         bitL->n_ch = arr_ch;  
  19.         bitL->n_bit = arr_c_b;  
  20.         bitL->n_arr = 0;  
  21.         bitL->n_ch = 0;  
  22.         bitL->n_bit = 0;  
  23. 這樣的話setBitValue()也就簡單多了:
  24. /*      把某位設為n                      * 
  25. void setBitValue(Bits arr[],bitLoca *bitL)  
  26.     arr[bitL->n_arr].ele_ch[bitL->n_ch] = arr[bitL->n_arr].ele_ch[bitL->n_ch] | arr2n[bitL->n_bit];  
  27. 寫到這裡,輸出不重複的随機數部分就能跑了,不過有一個小問題,有關rand()函數的。
  28. int __cdecl rand(void);  

    rand()函數産生一個0到RAND_MAX的僞随機數,而在樓主的系統裡,RAND_MAX是32767,就是2^15。是以題目要求的k<1000000就死活打不成了,為了解決這個問題,我不假思索的寫了這樣一條語句:

  29. int value = rand()*rand()%n;  

    再跑一次,ok,解決了…(\簡單粗暴)

運作一下。

位圖排序思想及代碼詳解

    ok,第一部分——找随機數解決。

三、排序

    其實到了現在這一步,這已經不适合叫排序了,倒更像是周遊。從0開始,把申請到的記憶體全過一遍,值為1 就輸出。這一步隻需要注意邊界的問題,用for循環就可以搞定。

    不過在這個地方我還是用的簡單粗暴(不動腦子)的辦法,先求出最後一個數字所在的位置(arr\ch\bit),再分情況依次周遊,是以寫的又長又臭。

  1. void bitSort(Bits arr[],int n)  
  2.     int max_arr = n/32;  
  3.     int tbit = n-max_arr*32;  
  4.     short max_ch = 0;   //在第幾個char裡   
  5.     if(tbit)  
  6.         max_ch = tbit/8;  
  7.     int max_bit = tbit%8;  
  8.     for(int tarr = 0;tarr<=max_arr;tarr++)  
  9.         if(tarr != max_arr) //全滿的數組   
  10.             for(int tch = 0;tch<4;tch++)  
  11.                 {  
  12.                     for(int tbit = 0;tbit<8;tbit++)  
  13.                     {  
  14.                         if(arr[tarr].ele_ch[tch] & arr2n[tbit])  
  15.                         {  
  16.                             printf("%d ",tarr*32+tch*8+tbit);  
  17.                         }  
  18.                     }  
  19.                 }     
  20.         else    //非全滿的數組   
  21.             for(int tch = 0;tch<=max_ch;tch++)  
  22.                 if(tch != max_ch)   //全滿的ch   
  23.                         printf("tarr = %d ,tch = %d ,tbit = %d : \n",tarr,tch,tbit);  
  24.                 }  
  25.                 else    //非全滿ch   
  26.                     for(int tbit = 0;tbit<max_bit;tbit++)  
  27. 再運作一遍:
    位圖排序思想及代碼詳解

四、下一步

    倉促之下完成的代碼,不可避免的留下了許多可以提升的空間,以下幾點是重構、優化時必須要考慮的問題。

    1.随機數分布不均的問題:rand()*rand()簡單粗暴,不過從機率的角度上講隻能稱之為無腦。

    2.周遊的優化。

    3.代碼的封裝:尋找一個數的位址的功能可以封裝成一個函數。

    4.位運算可以用位移操作代替。

五、代碼

  1. /* 
  2.     163_union_1_sort.c 
  3.     author:Magic激流 
  4.     2018-5-26  
  5. */  
  6. #include <stdio.h>  
  7. #include <stdlib.h>  
  8. #include <time.h>  
  9. #include <windows.h>  
  10. #include <stdbool.h>  
  11.     short element[2];  
  12.     char ele_ch[4];  
  13. }Bits;  
  14. int arr2n[8]={1,2,4,8,16,32,64,128};  
  15. *       傳回:bit[n]的值 */  
  16.         int arr_n = num/32;  
  17.                 for(int tbit = 0;tbit<8;tbit++)  
  18.                     if(arr[tarr].ele_ch[tch] & arr2n[tbit])  
  19.                         printf("%d ",tarr*32+tch*8+tbit);  
  20. int main (void)  
  21.     srand(time(NULL));  
  22.     while(k)  
  23.         bitLoca bitL;  
  24.         int value = rand()*rand()%n;  
  25. //      int value = rand()%n;  
  26.         if(bitValue(value,arr,&bitL)==0)  
  27.             setBitValue(arr,&bitL);  
  28.             printf("%d ",value);  
  29.             k--;  
  30.     printf("\nSort:\n");  
  31.     bitSort(arr,n);  
  32.     return 0;  

繼續閱讀