zoukankan      html  css  js  c++  java
  • 第2章 数字之魅——求二进制中1的个数

    求二进制中1的个数

    问题描述

      对于一个字节(8bit)的变量,求其二进制表示中“1”的个数,要求算法的执行效率尽可能地高。

    【解法一】

      可以举一个八位的二进制例子来进行分析。对于二进制操作,我们知道,除以一个2,原来的数字将会减少一个0。如果除的过程中有余,那么就表示当前位置有一个1。

    以10 100 010为例;

    第一次除以2时,商为1 010 001,余为0。

    第二次除以2时,商为101 000,余为1。

    因此,可以考虑利用整型数据除法的特点,通过相除和判断余数的值来进行分析。于是有了如下的代码:

     1 package chapter2shuzizhimei.erjinzhicount1;
     2 /**
     3  * 求二进制数中1的个数
     4  * 【方法一】
     5  * @author DELL
     6  *
     7  */
     8 public class Count1 {
     9     
    10     /**
    11      * 统计整数x转变成二进制后1的个数
    12      * @param x 被计算的整数
    13      * @return 二进制中1的个数
    14      */
    15     public static int count(int x){
    16         int count = 0;  //记录二进制中1的个数
    17         while(x!=0){
    18             if(x%2==1){
    19                 count++;
    20             }
    21             x /= 2;
    22         }
    23         return count;
    24     }
    25     
    26     public static void main(String[] args){
    27         int x = 15;
    28         System.out.println("二进制中1的个数为:"+count(x));
    29     }
    30 }

    程序运行结果如下:

    二进制中1的个数为:4

     【解法二】采用位操作

      前面的代码看起来比较复杂。我们知道,向右移位操作同样也可以达到相除的目的。唯一不同之处在于,移位之后如何来判断是否有1存在。对于这个问题,再来看看一个八位的数字:10 100 001。

      在向右移位的过程中,我们会把最后一位直接丢弃。因此,需要判断最后一位是否为1,而"与"操作可以达到目的。可以把这个八位的数字与00000001进行"与"操作。如果结果为1,则表示当前八位数的最后一位为1,否则为0。代码如下:

     1 package chapter2shuzizhimei.erjinzhicount1;
     2 /**
     3  * 求二进制数中1的个数
     4  * 【方法二】采用位操作
     5  * @author DELL
     6  *
     7  */
     8 public class Count2 {
     9     
    10     /**
    11      * 统计整数x转变成二进制后1的个数
    12      * @param x 被计算的整数
    13      * @return 二进制中1的个数
    14      */
    15     public static int count(int x){
    16         int count = 0;  //记录二进制中1的个数
    17         while(x!=0){
    18             count += x&0x01;  //采用与运算来统计1的个数
    19             x >>= 1;
    20         }
    21         return count;
    22     }
    23     
    24     public static void main(String[] args){
    25         int x = 15;
    26         System.out.println("二进制中1的个数为:"+count(x));
    27     }
    28 }

    程序运行结果如下:

    二进制中1的个数为:4

     【解法三】 只考虑1的个数

      位操作比除、余操作的效率高了很多。但是,即使采用位操作,时间复杂度仍为O(log2v),log2v为二进制数的位数。那么,还能不能再降低一些复杂度呢?如果有办法让算法的复杂度只与"1"的个数有关,复杂度不就能进一步降低了吗?

      同样用10 100 001来举例。如果只考虑和1的个数相关,那么,我们是否能够在每次判断中,仅与1来进行判断呢?

      为了简化这个问题,我们考虑只有一个1的情况。例如:01 000 000。

      如何判断给定的二进制数里面有且仅有一个1呢?可以通过判断这个数是否是2的整数次幂来实现。另外,如果只和这一个"1"进行判断,如何设计操作呢?我们知道的是,如果进行这个操作,结果为0或为1,就可以得到结论。

      如果希望操作后的结果为0,01 000 000可以和00 111 111进行"与"操作。

      这样,要进行的操作就是 01 000 000 &(01 000 000 - 00 000 001)= 01 000 000 &00 111 111 = 0。

      因此就有了解法三的代码:

     1 package chapter2shuzizhimei.erjinzhicount1;
     2 /**
     3  * 求二进制数中1的个数
     4  * 【方法三】只考虑1的个数减少时间复杂度
     5  * @author DELL
     6  *
     7  */
     8 public class Count3 {
     9     
    10     /**
    11      * 统计整数x转变成二进制后1的个数
    12      * @param x 被计算的整数
    13      * @return 二进制中1的个数
    14      */
    15     public static int count(int x){
    16         int count = 0;  //记录二进制中1的个数
    17         while(x!=0){
    18             x &= (x-1);  //消掉最高位的1
    19             count++;
    20         }
    21         return count;
    22     }
    23     
    24     public static void main(String[] args){
    25         int x = 15;
    26         System.out.println("二进制中1的个数为:"+count(x));
    27     }
    28 }

    程序运行结果如下:

    二进制中1的个数为:4

    【解法四】使用分支操作

      解法三的复杂度降低到O(M),其中M是v中1的个数,可能会有人已经很满足了,只用计算1的位数,这样应该够快了吧。然而我们说既然只有八位数据,索性直接把0~255的情况都罗列出来,并使用分支操作,可以得到答案,代码如下:

     1 int count(int x)  
     2 {  
     3     int num = 0;  
     4     switch (x)  
     5     {  
     6         case 0x0:  
     7             num = 0;  
     8             break;  
     9         case 0x1:  
    10         case 0x2:  
    11         case 0x4:  
    12         case 0x8:  
    13         case 0x10:  
    14         case 0x20:  
    15         case 0x40:  
    16         case 0x80:  
    17             num = 1;  
    18             break;  
    19         case 0x3:  
    20         case 0x6:  
    21         case 0xc:  
    22         case 0x18:  
    23         case 0x30:  
    24         case 0x60:  
    25         case 0xc0:  
    26             num = 2;  
    27             break;  
    28             //...  
    29     }  
    30     return num;  
    31 } 

      解法四看似很直接,但实际执行效率可能会低于解法二和解法三,因为分支语句的执行情况要看具体字节的值,如果a =0,那自然在第1个case就得出了答案,但是如果a =255,则要在最后一个case才得出答案,即在进行了255次比较操作之后!

      看来,解法四不可取!但是解法四提供了一个思路,就是采用空间换时间的方法,罗列并直接给出值。如果需要快速地得到结果,可以利用空间或利用已知结论。这就好比已经知道计算1+2+ … +N的公式,在程序实现中就可以利用公式得到结论。

    【解法五】查表法

      算法中不需要进行任何的比较便可直接返回答案,这个解法在时间复杂度上应该能够让人高山仰止了。

      代码如下:

     1 int countTable[256] =     
     2 {        
     3          0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4,
     4          1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
     5          1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
     6          2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
     7          1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
     8          2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
     9          2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
    10          3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
    11          1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
    12          2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
    13          2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
    14          3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
    15          2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
    16          3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
    17          3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
    18          4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8        
    19 };
    20 
    21 int count(int v)
    22 {
    23     return countTable[v];
    24 }

      这是个典型的空间换时间的算法,把0~255中"1"的个数直接存储在数组中,v作为数组的下标,countTable[v]就是v中"1"的个数。算法的时间复杂度仅为O(1)。

      在一个需要频繁使用这个算法的应用中,通过"空间换时间"来获取高的时间效率是一个常用的方法,具体的算法还应针对不同应用进行优化。

    扩展问题

    1. 如果变量是32位的DWORD,你会使用上述的哪一个算法,或者改进哪一个算法?

    2. 另一个相关的问题,给定两个正整数(二进制形式表示)A和B,问把A变为B需要改变多少位(bit)?也就是说,整数A 和B 的二进制表示中有多少位是不同的?

      这个问题其实就是比问题1多了一个步骤,只要先算出A和B的异或结果,然后求这个异或值中1的个数就行了。

  • 相关阅读:
    Codeforces Round 546 (Div. 2)
    Codeforces Round 545 (Div. 2)
    Codeforces Round 544(Div. 3)
    牛客小白月赛12
    Codeforces Round 261(Div. 2)
    Codeforces Round 260(Div. 2)
    Codeforces Round 259(Div. 2)
    Codeforces Round 258(Div. 2)
    Codeforces Round 257 (Div. 2)
    《A First Course in Probability》-chaper5-连续型随机变量-随机变量函数的分布
  • 原文地址:https://www.cnblogs.com/gaopeng527/p/4614605.html
Copyright © 2011-2022 走看看