【问题标题】:Number of Zeros in the binary representation of an Integer [duplicate]整数的二进制表示中的零数[重复]
【发布时间】:2009-11-22 03:12:46
【问题描述】:

可能重复:
Best algorithm to count the number of set bits in a 32-bit integer?

给定一个 32 位整数 N,设计一个算法来找出 N 的二进制位表示中的零的个数。

我能想到的最简单的算法是检查零的二进制表示,在 C 中是这样的:

int num_of_zero(int num)
 {
   if(0 == num) return 1; /*For the input 0 it should output 1 */
   int Count = 0;
   while(num>0){
     if(0 == (num&1)) Count++;
    num >>= 1;
}
return Count;
}

如果有一些算法可以在恒定时间计算,我正在徘徊。

对于输入 0,它应该返回 1 而不是 32

对于5,输出应该是1。因为二进制表示是101

对于 7,输出应为 0。

确切地说,我正在寻找一种更好的算法来计算 32 位整数的二进制解释中(非前导)零的数量。希望问题现在很清楚。

编辑:正如 Alex Martelli 指出的那样,delroth 我正在修改我的代码以使其更具可读性并这次使用迭代。

【问题讨论】:

  • num_of_zero(num >> 1); if(!(num & 1))Count++;你能把它分解吗?我看不出这将如何工作。我错过了什么明显的东西吗?
  • 在 OP 有机会回答问题之前,我不太喜欢标记作业。对于自学的人来说,SO 有很多先例。而且,如果它家庭作业并且他们使用从网上逐字找到的解决方案,他们几乎肯定会因为剽窃而失败(如果他们的教育者有一点点智慧的话)。
  • @nthrgeek,如果这个作业,请标记它。
  • @paxdiablo:不,这不是家庭作业问题,我正在寻找更好的算法,因为这是 O(n)。

标签: c algorithm


【解决方案1】:

执行此操作的简单方法是遍历数字的二进制表示的每一位,测试每一位的值,并计算其中有多少为零。循环会比递归更清晰。

不过,还有许多更优化的方法可以做到这一点。您可以在"Best algorithm to count the number of set bits in a 32-bit integer" 这个问题的答案中找到一些更好的答案(显然,零位数是从总位数中减去设置位数)。

【讨论】:

  • 这更简单,我知道,还有更好的算法吗?就像我们为设置位设置的一样?
  • 您可以使用这些算法中的任何一种,因为设置的位数和未设置的位数之和就是总位数。如果您想知道非前导零的个数,可以使用该数字的 log-base-2 来确定最高设置位的索引;也有一些算法可以快速找到它。
  • Bit Twiddling Hacks 网站,Suppressingfire 在他的回答中提到,有多种方法可以找到 log-base-2:graphics.stanford.edu/~seander/bithacks.html#IntegerLogObvious
  • 感谢和 +1 让我知道 log-base-2 的事情 :)。但是你能帮我理解这如何有助于降低这个问题的复杂性吗?
  • 无符号整数 N 中非前导零的数量与 [ ((2^(lg2(N))-1) & (~N) ] 中设置的位数相同.
【解决方案2】:

有一个很棒的在线资源Bit Twiddling Hacks,其中包含各种很棒的 C 小技巧。您可能对Counting bits set 部分特别感兴趣。

【讨论】:

    【解决方案3】:

    快速而愚蠢的方式——在重复的问题中有更多奇特的实现,但我过去曾使用过类似的东西而没有太大的不良影响。

    我们在这里使用一个半字节表来减少循环运行的次数——如果你正在做大量的这些计算,那么构建一个更大的数组可能会更有效,比如说,在字节水平,将循环切成两半。

    /* How many bits are set in every possible nibble. */
    static size_t BIT_TABLE[] = {
        0, 1, 1, 2,     /* 0, 1, 2, 3 */
        1, 2, 2, 3,     /* 4, 5, 6, 7 */
        1, 2, 2, 3,     /* 8, 9, A, B */
        2, 3, 3, 4      /* C, D, E, F */
    };
    
    size_t num_of_bits(int num) {
        /* Little reworking could probably shrink the stack space in use here. */
        size_t ret = 0, i;
        register int work = num;
    
        /* Iterate every nibble, rotating the integer in place. */
        for(i = 0; i < (sizeof(num) * 2); i++) {
            /* Pointer math to get a member of the static array. */
            ret += *(BIT_TABLE + (work & 0xF));
            work >>= 4;
        }
        return ret;
    }
    

    【讨论】:

      【解决方案4】:

      递归绝对是矫枉过正——此外,您的代码有很多错误(它不会计算num 的任何前导零!!!)。一个简单的迭代,如:

      int num_of_zero(int num) {
        unsigned int unum = (unsigned int)num;
        int count;
        int i;
      
        for(i = 0; i < 32; ++i) {
          if(!(unum & 1)) ++count;
          unum >>= 1;
        }
        return count;
      }
      

      正确且速度更快(可以编码更简洁,但我认为这是最清晰的表达方式)。

      如果您必须多次执行此计算,请考虑预先计算一个(例如)256 个“零计数”的数组(每个值给出其索引的计数,包括 0 到 255,作为 8 位数字)。然后,您可以只循环 4 次(一次屏蔽和移动 8 位),并轻松展开循环 - 如果您的编译器不够聪明,无法代表您执行此操作;-)。

      【讨论】:

      • 谢谢,我不想计算任何前导零。
      • 很有趣,您在编辑问题时如何彻底改变了您的规格——首先表达正确规格不是更好吗?!另外,如果您不想计算 任何 个前导零,正如您现在所说,0 的结果怎么可能是 1,因为您现在声称它应该在您最近编辑的答案中?您要计算的那个零“领先”。猜猜你将不得不对一个奇怪的异常值进行特殊处理——你确实想要计算前导 0 的唯一情况!-) 除此之外,while(unum) 作为循环而不是for &c 可以正常工作。
      • 抱歉,我当时只尝试编辑问题,但每次都收到 503 错误!!
      • 是的,我也有很长一段时间的 500 个。无论如何,是的,它最终是相似的,除了递归之外的其他荒谬,例如static
      • 奇怪的是我的评论消失了?!但当然感谢:)
      【解决方案5】:

      我猜这是一道作业题。没问题!这是最快的解决方案(经过长时间的启动成本):

      创建一个长度为 232byte 数组。为每个可能的int 值预先计算二进制表示中的零数的值以填充该数组。从那时起,您将拥有一个数组,该数组将为您提供每个值的零数。

      是的,这个解决方案很愚蠢——做很多工作却收效甚微——但是将它与另一个想法结合起来:

      如果您只预先计算 8 位长的值会发生什么?您能否编写代码,尽管速度不快,但仍会返回 int 中 0 的位数?

      如果您只预先计算 4 位长的值会发生什么? 2位长? 1 位长?

      我希望这能给您提供更好算法的想法...

      【讨论】:

      • Crikey,一个 40 亿字节的数组。我正要对此投反对票,直到我看到你要去哪里:-)
      • 我在问题第一次出现时写了这个评论。 Stackoverflow 在我提交之前就崩溃了。当我回来时,其他人也回答了这个问题。我希望我的答案仍然出现。而且,是的,paxdiablo - 最初的答案很愚蠢。但我曾希望它能提供一些更好的想法。
      • 也许 SO 崩溃了,因为它试图计算 4-gig 查找表时内存不足... :-) 无论如何,我认为这是一个很好的答案 (+1)。
      【解决方案6】:

      这并不是你主要问题的真正答案,但你应该像这样重写你的递归函数:

      int num_of_zero(int num)
      {
          int left_part_zeros;
      
          if (num == 0)
              return 0;
      
          left_part_zeros = num_of_zero(num >> 1);
          if ((num & 1) == 0)
              return left_part_zeros + 1;
          else
              return left_part_zeros;
      }
      

      除了完全不可读之外,您的实现还有很多问题。

      【讨论】:

        【解决方案7】:

        我发现的最简单的方法是将它建立在计算个数的基础上,然后简单地从 32 中减去它(假设您确定int 的大小是 32 位)。

        int numberOfOnes (int num) {
            int count = 0;
            unsigned int u = (unsigned int)num;
            while (u != 0) {
                if ((u&1) == 1)
                    count++;
                u >>= 1;
            }
            return count;
        }
        int numberOfZeros (int num) {
            return 32 - numberOfOnes (num);
        }
        

        这实际上为您提供了两种变体(一和零) - 有更快的方法可以做到这一点,但我不会考虑它们,除非并且直到您知道存在性能问题。我倾向于首先编写可读性代码。

        您可能还想至少测试一下查找表可能更快的可能性(优化的主要指令是衡量,不要猜测!

        一种可能是用一次操作一个 nybble 的东西替换 numberOfOnes 函数:

        int numberOfOnes (int num) {
            static const count[] = {
                0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4
            };
            int retval = 0;
            unsigned int u = (unsigned int)num;
            while (u != 0) {
                retval += count[u & 0x0f]
                u >>= 4;
            }
            return retval;
        }
        

        【讨论】:

        • 如果这是我要找的,我最好使用 while(num) { count++;num &=(num - 1) } 来计算设置位数。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-01-21
        • 2019-02-04
        • 2018-03-17
        • 2022-01-16
        • 1970-01-01
        • 2016-08-22
        • 2019-08-04
        相关资源
        最近更新 更多