位图排序思想及代码详解

输入:一个最多包含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.     
  6.     int n = 0;  
  7.     printf("Please input range:\n");  
  8.     scanf("%d",&n);   
  9.     const int N = n/32+1;  
  10.     Bits arr[N];  
  11.         
  12.     memset(arr,0,sizeof(arr));  
  13.         
  14.     int k = 0;  
  15.         
  16.     printf("Please input count of number,");  
  17.     do{  
  18.         printf("this number is less than %d\n",n);  
  19.         scanf("%d",&k);  
  20.     }while(k>n);  

所以,当生成了一个随机数num后,就把内存中第num位置为1,以表示该数已存在,同理,检查是否有重复时,只需看该位的值。

对检查该位的值以及修改该位的值这两个操作,用两个函数表示。

  1. /*      查看该位是否为0        * 
  2. *       输入:一个整数n        * 
  3. *       返回:bit[n]的值         */  
  4. bool bitValue(int num,Bits arr[],...);  
  5.  
  6.  
  7. /*      把某位设为n                  * 
  8. *       输入:一个整数n代表的地址       * 
  9. *       操作:将该地址上的值设为1       */  
  10. 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.     {  
  5.         int arr_n = num/(32);   //在第几个数组   
  6.         int arr_b = num-arr_n*32;  
  7.         short arr_ch = 0;   //在第几个char里   
  8.         if(arr_b)  
  9.         {  
  10.             arr_ch = arr_b/8;  
  11.         }  
  12.             
  13.         int arr_c_b = arr_b;    //第几个bit   
  14.         while(arr_c_b>=8)  
  15.         {  
  16.             arr_c_b -= 8;  
  17.         }  
  18.                 
  19.         return (arr[arr_n].ele_ch[arr_ch] & arr2n[arr_c_b]);  
  20.     }  
  21.     else    //处理0   
  22.     {  
  23.         return (arr[num].ele_ch[0] & arr2n[0]);   
  24.     }  
  25.     
  26. }

    又因为是以8bit为一单位,所以创建数组全局数组 arr2n[8],用来存储2^0~2^7的值。在小端模式下,恰好是左第一位为2^0。

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

    所以void setBitValue()也和bool bitValue()过程类似,找到n在内存中的位置,在更新它的值。不过在这个函数里重复计算位置不划算,所以我们可以在第一次计算时把位置信息保存起来,要更新数据时再传给函数。

    这里申明一个位置结构,用来存储一个数字在内存中相对于数组结构的位置。

  28. typedef struct bitLoca{  
  29.     int n_arr;  
  30.     int n_ch;  
  31.     int n_bit;  
  32. }bitLoca;  

然后在在主函数的while循环里加上它,并修改两个函数的参数:

  1. int main(void)  
  2. {  
  3.     //...  
  4.     
  5.     while(key)  
  6.         {  
  7.             bitLoca bitL;  
  8.             int value = rand()%n;  
  9.             if(bitValue(value,arr,&bitL)==0)  
  10.             {  
  11.                 setBitValue(arr,&bitL);  
  12.                 printf("%d\n",value);  
  13.                 key--;  
  14.             }  
  15.         }  
  16.         
  17.     //...   
  18. }  

    更新后的bitValue():

  19. bool bitValue(int num,Bits arr[],bitLoca *bitL)  
  20. {     
  21.     if(num!=0)  
  22.     {  
  23.         int arr_n = num/(32);  
  24.         int arr_b = num-arr_n*32;  
  25.         short arr_ch = 0;   //在第几个char里   
  26.         if(arr_b)  
  27.         {  
  28.             arr_ch = arr_b/8;  
  29.         }  
  30.             
  31.         int arr_c_b = arr_b;  
  32.         while(arr_c_b>=8)  
  33.         {  
  34.             arr_c_b -= 8;  
  35.         }  
  36.             
  37.         bitL->n_arr = arr_n;  
  38.         bitL->n_ch = arr_ch;  
  39.         bitL->n_bit = arr_c_b;  
  40.             
  41.         return (arr[arr_n].ele_ch[arr_ch] & arr2n[arr_c_b]);  
  42.     }  
  43.     else    //处理0   
  44.     {  
  45.         bitL->n_arr = 0;  
  46.         bitL->n_ch = 0;  
  47.         bitL->n_bit = 0;  
  48.         return (arr[num].ele_ch[0] & arr2n[0]);   
  49.     }  
  50. }  

    这样的话setBitValue()也就简单多了:

  51. /*      把某位设为n                      * 
  52. *       输入:一个整数n代表的地址       * 
  53. *       操作:将该地址上的值设为1       */  
  54. void setBitValue(Bits arr[],bitLoca *bitL)  
  55. {  
  56.     arr[bitL->n_arr].ele_ch[bitL->n_ch] = arr[bitL->n_arr].ele_ch[bitL->n_ch] | arr2n[bitL->n_bit];  
  57. }  

    写到这里,输出不重复的随机数部分就能跑了,不过有一个小问题,有关rand()函数的。

  58. int __cdecl rand(void);  

    rand()函数产生一个0到RAND_MAX的伪随机数,而在楼主的系统里,RAND_MAX是32767,就是2^15。所以题目要求的k<1000000就死活打不成了,为了解决这个问题,我不假思索的写了这样一条语句:

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

    再跑一次,ok,解决了…(\简单粗暴)

运行一下。

ok,第一部分——找随机数解决。

三、排序

其实到了现在这一步,这已经不适合叫排序了,倒更像是遍历。从0开始,把申请到的内存全过一遍,值为1 就输出。这一步只需要注意边界的问题,用for循环就可以搞定。

不过在这个地方我还是用的简单粗暴(不动脑子)的办法,先求出最后一个数字所在的位置(arr\ch\bit),再分情况依次遍历,所以写的又长又臭。

  1. void bitSort(Bits arr[],int n)  
  2. {  
  3.     int max_arr = n/32;  
  4.     int tbit = n-max_arr*32;  
  5.     short max_ch = 0;   //在第几个char里   
  6.     if(tbit)  
  7.     {  
  8.         max_ch = tbit/8;  
  9.     }  
  10.     int max_bit = tbit%8;  
  11.     
  12.     for(int tarr = 0;tarr<=max_arr;tarr++)  
  13.         if(tarr != max_arr) //全满的数组   
  14.         {  
  15.             for(int tch = 0;tch<4;tch++)  
  16.                 {  
  17.                     for(int tbit = 0;tbit<8;tbit++)  
  18.                     {  
  19.                         if(arr[tarr].ele_ch[tch] & arr2n[tbit])  
  20.                         {  
  21.                             printf("%d ",tarr*32+tch*8+tbit);  
  22.                         }  
  23.                     }  
  24.                 }     
  25.         }  
  26.         else    //非全满的数组   
  27.         {  
  28.             for(int tch = 0;tch<=max_ch;tch++)  
  29.                 if(tch != max_ch)   //全满的ch   
  30.                 {  
  31.                     for(int tbit = 0;tbit<8;tbit++)  
  32.                     {  
  33.                         printf("tarr = %d ,tch = %d ,tbit = %d : \n",tarr,tch,tbit);  
  34.                         if(arr[tarr].ele_ch[tch] & arr2n[tbit])  
  35.                         {  
  36.                             printf("%d ",tarr*32+tch*8+tbit);  
  37.                         }  
  38.                     }  
  39.                 }  
  40.                 else    //非全满ch   
  41.                 {  
  42.                     for(int tbit = 0;tbit<max_bit;tbit++)  
  43.                     {  
  44.                         if(arr[tarr].ele_ch[tch] & arr2n[tbit])  
  45.                         {  
  46.                             printf("%d ",tarr*32+tch*8+tbit);  
  47.                         }  
  48.                     }  
  49.                 }  
  50.         }  
  51. }  

    再运行一遍:

四、下一步

仓促之下完成的代码,不可避免的留下了许多可以提升的空间,以下几点是重构、优化时必须要考虑的问题。

    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.     
  12. typedef union str{  
  13.     short element[2];  
  14.     char ele_ch[4];  
  15. }Bits;  
  16.     
  17. typedef struct bitLoca{  
  18.     int n_arr;  
  19.     int n_ch;  
  20.     int n_bit;  
  21. }bitLoca;  
  22.     
  23. int arr2n[8]={1,2,4,8,16,32,64,128};  
  24.     
  25. /*      查看该位是否为0        * 
  26. *       输入:一个整数n        * 
  27. *       返回:bit[n]的值 */  
  28. bool bitValue(int num,Bits arr[],bitLoca *bitL)  
  29. {     
  30.     if(num!=0)  
  31.     {  
  32.         int arr_n = num/32;  
  33.         int arr_b = num-arr_n*32;  
  34.         short arr_ch = 0;   //在第几个char里   
  35.         if(arr_b)  
  36.         {  
  37.             arr_ch = arr_b/8;  
  38.         }  
  39.             
  40.         int arr_c_b = arr_b;  
  41.         while(arr_c_b>=8)  
  42.         {  
  43.             arr_c_b -= 8;  
  44.         }  
  45.             
  46.         bitL->n_arr = arr_n;  
  47.         bitL->n_ch = arr_ch;  
  48.         bitL->n_bit = arr_c_b;  
  49.             
  50.         return (arr[arr_n].ele_ch[arr_ch] & arr2n[arr_c_b]);  
  51.     }  
  52.     else    //处理0   
  53.     {  
  54.         bitL->n_arr = 0;  
  55.         bitL->n_ch = 0;  
  56.         bitL->n_bit = 0;  
  57.             
  58.         return (arr[num].ele_ch[0] & arr2n[0]);   
  59.     }  
  60. }  
  61.     
  62. /*      把某位设为n                      * 
  63. *       输入:一个整数n代表的地址       * 
  64. *       操作:将该地址上的值设为1       */  
  65. void setBitValue(Bits arr[],bitLoca *bitL)  
  66. {  
  67.     arr[bitL->n_arr].ele_ch[bitL->n_ch] = arr[bitL->n_arr].ele_ch[bitL->n_ch] | arr2n[bitL->n_bit];  
  68. }  
  69.     
  70. void bitSort(Bits arr[],int n)  
  71. {  
  72.     int max_arr = n/32;  
  73.     int tbit = n-max_arr*32;  
  74.     short max_ch = 0;   //在第几个char里   
  75.     if(tbit)  
  76.     {  
  77.         max_ch = tbit/8;  
  78.     }  
  79.     int max_bit = tbit%8;  
  80.     
  81.     for(int tarr = 0;tarr<=max_arr;tarr++)  
  82.         if(tarr != max_arr) //全满的数组   
  83.         {  
  84.             for(int tch = 0;tch<4;tch++)  
  85.                 for(int tbit = 0;tbit<8;tbit++)  
  86.                 {  
  87.                     if(arr[tarr].ele_ch[tch] & arr2n[tbit])  
  88.                     {  
  89.                         printf("%d ",tarr*32+tch*8+tbit);  
  90.                     }  
  91.                 }  
  92.         }  
  93.         else    //非全满的数组   
  94.         {  
  95.             for(int tch = 0;tch<=max_ch;tch++)  
  96.                 if(tch != max_ch)   //全满的ch   
  97.                 {  
  98.                     for(int tbit = 0;tbit<8;tbit++)  
  99.                     {  
  100.                         if(arr[tarr].ele_ch[tch] & arr2n[tbit])  
  101.                         {  
  102.                             printf("%d ",tarr*32+tch*8+tbit);  
  103.                         }  
  104.                     }  
  105.                 }  
  106.                 else    //非全满ch   
  107.                 {  
  108.                     for(int tbit = 0;tbit<max_bit;tbit++)  
  109.                     {  
  110.                         if(arr[tarr].ele_ch[tch] & arr2n[tbit])  
  111.                         {  
  112.                             printf("%d ",tarr*32+tch*8+tbit);  
  113.                         }  
  114.                     }  
  115.                 }  
  116.         }  
  117. }  
  118.     
  119. int main (void)  
  120. {  
  121.     int n = 0;  
  122.     printf("Please input range:\n");  
  123.     scanf("%d",&n);   
  124.     const int N = n/32+1;  
  125.     Bits arr[N];  
  126.         
  127.     memset(arr,0,sizeof(arr));  
  128.         
  129.     int k = 0;  
  130.         
  131.     printf("Please input count of number,");  
  132.     do{  
  133.         printf("this number is less than %d\n",n);  
  134.         scanf("%d",&k);  
  135.     }while(k>n);  
  136.     
  137.     srand(time(NULL));  
  138.         
  139.     while(k)  
  140.     {  
  141.         bitLoca bitL;  
  142.         int value = rand()*rand()%n;  
  143. //      int value = rand()%n;  
  144.         if(bitValue(value,arr,&bitL)==0)  
  145.         {  
  146.             setBitValue(arr,&bitL);  
  147.             printf("%d ",value);  
  148.             k--;  
  149.         }  
  150.     }  
  151.         
  152.     printf("\nSort:\n");  
  153.         
  154.     bitSort(arr,n);  
  155.         
  156.     return 0;  
  157. }  

原文地址:https://www.cnblogs.com/magicxyx/p/9092701.html

时间: 2024-07-31 08:23:50

位图排序思想及代码详解的相关文章

数据结构 - 简单选择排序(simple selection sort) 详解 及 代码(C++)

数据结构 - 简单选择排序(simple selection sort) 本文地址: http://blog.csdn.net/caroline_wendy/article/details/28601965 选择排序(selection sort) : 每一趟在n-i+1个记录中选取关键字最小的记录作为有序序列中第i个记录. 简单选择排序(simple selection sort) : 通过n-i次关键字之间的比较, 从n-i+1个记录中选出关键字最小的记录, 并和第i个记录交换. 选择排序需

tiny_cnn代码详解(3)——层间继承关系

在上一篇博文中我们顺利将tiny_cnn的程序调试通过,在这篇博文中我们尝试从整体角度给出对tiny_cnn这个深度学习框架的解读,重点论述一下其各个层直接类封装的继承关系. 一.卷积神经网络快速入门 tiny_cnn作为卷积神经网络的一种实现形式,在探讨其框架结构之前,首先需要简要介绍一些卷积神经网络相关的知识.首先,给出经典卷积神经网络的网络结构: 这个是经典的LeNet-5的网络结构图,五层网络.最早用于支票上的手写数字识别,也是最早的商业化的深度学习模型.从上图中可以看出,卷积神经网络主

Github-jcjohnson/torch-rnn代码详解

Github-jcjohnson/torch-rnn代码详解 [email protected] http://www.cnblogs.com/swje/ 作者:Zhouwan  2016-3-18 声明: 1)本文仅供学术交流,非商用.所以每一部分具体的参考资料并没有详细对应.如果某部分不小心侵犯了大家的利益,还望海涵,并联系博主删除. 2)本人才疏学浅,整理总结的时候难免出错,还望各位前辈不吝指正,谢谢. 请联系:[email protected] 或[email protected] 本研

jQuery选择器代码详解(四)——Expr.preFilter

原创文章,转载请注明出处,多谢! Expr.preFilter是tokenize方法中对ATTR.CHILD.PSEUDO三种选择器进行预处理的方法.具体如下: Expr.preFilter : { "ATTR" : function(match) { /* * 完成如下任务: * 1.属性名称解码 * 2.属性值解码 * 3.若判断符为~=,则在属性值两边加上空格 * 4.返回最终的mtach对象 * * match[1]表示属性名称, * match[1].replace(rune

JQuery选择器代码详解(三)——tokenize方法

原创文章,转载请注明出处,多谢! /* * tokenize函数是选择器解析的核心函数,它将选择器转换成两级数组groups * 举例: * 若选择器为"div.class,span",则解析后的结果为: * group[0][0] = {type:'TAG',value:'div',matches:match} * group[0][1] = {type:'CLASS',value:'.class',matches:match} * group[1][0] = {type:'TAG'

开胃小菜——impress.js代码详解

README 友情提醒,下面有大量代码,由于网页上代码显示都是同一个颜色,所以推荐大家复制到自己的代码编辑器中看. 今天闲来无事,研究了一番impress.js的源码.由于之前研究过jQuery,看impress.js并没有遇到太大的阻碍,读代码用了一个小时,写这篇文章用了近三个小时,果然写文章比读代码费劲多了. 个人感觉impress.js的代码量(算上注释一共不到1000行)和难度(没有jQuery的各种black magic= =)都非常适合新手学习,所以写一个总结,帮助大家理解源码. 考

jQuery选择器代码详解(七)——elementMatcher函数

要读懂Sizzle的Compile执行过程,首先需要弄清楚涉及的各个子程序的功能和关键变量和作用,我将逐一对jQuery-1.10.2版本的Compile代码进行说明,望能给予大家帮助. elementMatcher(matchers) 1.源码 function elementMatcher(matchers) { return matchers.length > 1 ? function(elem, context, xml) { var i = matchers.length; while

jQuery选择器代码详解(八)——addCombinator函数

function addCombinator(matcher, combinator, base) 1.源码 function addCombinator(matcher, combinator, base) { var dir = combinator.dir, checkNonElements = base && dir === "parentNode", doneName = done++; return combinator.first ? // Check a

jQuery选择器代码详解(五)——实例说明tokenize的解析过程

原创文章,转载请写明出处,多谢! 以下分析基于jQuery-1.10.2.js版本. 下面将以$("div:not(.class:contain('span')):eq(3)")为例,说明tokenize和preFilter各段代码是如何协调完成解析的.若想了解tokenize方法和preFilter类的每行代码的详细解释,请参看如下两篇文章: jQuery选择器代码详解(三)--tokenize方法 jQuery选择器代码详解(四)--Expr.preFilter 下面是tokeni