整数的二进制表示中的零个数

时间:2009-11-22 03:12:46

标签: c algorithm

  

可能重复:
  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指出的那样,我正在修改我的代码以使其更具可读性和安全性。这次使用迭代。

7 个答案:

答案 0 :(得分:5)

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

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

答案 1 :(得分:5)

网上有一个名为Bit Twiddling Hacks的优秀资源,其中包含各种精彩的小C技巧。您可能对Counting bits set部分特别感兴趣。

答案 2 :(得分:4)

快速而愚蠢的方式 - 在重复的问题中有更多异国情调的实现,但我使用过类似的东西,过去没有太大的影响。

我们在这里使用一个半字节表来减少循环运行的次数 - 如果你正在进行这些计算的大量工作,那么构建一个更大的数组(例如,在字节处)可能更有效水平,削减循环一半。

/* 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;
}

答案 3 :(得分:2)

递归肯定是矫枉过正 - 而且,你的代码非常错误(它不会计算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位),并轻松展开循环 - 如果你的编译器不够聪明,不能代表你这么做; - )。

答案 4 :(得分:2)

我猜这是一个家庭作业问题。没问题!这是最快的解决方案(在很长的启动成本之后):

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

是的,这个解决方案很愚蠢 - 这是一项很少收获的工作 - 但将其与另一个想法结合起来:

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

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

我希望这可以为你提供一个更好的想法......

答案 5 :(得分:1)

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

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;
}

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

答案 6 :(得分:1)

我找到的最简单的方法是将它基于那些计数的东西,然后简单地从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);
}

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

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

有一种可能性是将numberOfOnes函数替换为一次运行nybble的东西:

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;
}