【问题标题】:Bit hack to generate all integers with a given number of 1s位黑客生成具有给定数量 1 的所有整数
【发布时间】:2011-11-26 22:00:20
【问题描述】:

我忘了一点技巧来生成所有具有给定数量 1 的整数。有人记得吗(可能也可以解释一下)?

【问题讨论】:

    标签: bit combinatorics


    【解决方案1】:

    来自Bit Twiddling Hacks

    更新测试程序Live On Coliru

    #include <utility>
    #include <iostream>
    #include <bitset>
    
    using I = uint8_t;
    
    auto dump(I v) { return std::bitset<sizeof(I) * __CHAR_BIT__>(v); }
    
    I bit_twiddle_permute(I v) {
        I t = v | (v - 1); // t gets v's least significant 0 bits set to 1
        // Next set to 1 the most significant bit to change, 
        // set to 0 the least significant ones, and add the necessary 1 bits.
        I w = (t + 1) | (((~t & -~t) - 1) >> (__builtin_ctz(v) + 1));  
        return w;
    }
    
    int main() {
        I p = 0b001001;
        std::cout << dump(p) << "\n";
        for (I n = bit_twiddle_permute(p); n>p; p = n, n = bit_twiddle_permute(p)) {
            std::cout << dump(n) << "\n";
        }
    }
    

    打印

    00001001
    00001010
    00001100
    00010001
    00010010
    00010100
    00011000
    00100001
    00100010
    00100100
    00101000
    00110000
    01000001
    01000010
    01000100
    01001000
    01010000
    01100000
    10000001
    10000010
    10000100
    10001000
    10010000
    10100000
    11000000
    

    按字典顺序计算下一位排列

    假设我们在一个整数中有一个 N 位设置为 1 的模式,并且我们想要在字典学意义上的 N 1 位的下一个排列。例如,如果 N 为 3 且位模式为 00010011,则下一个模式将为 00010101、00010110、00011001、00011010、00011100、00100011 等。以下是计算下一个排列的快速方法。

    unsigned int v; // current permutation of bits 
    unsigned int w; // next permutation of bits
    
    unsigned int t = v | (v - 1); // t gets v's least significant 0 bits set to 1
    // Next set to 1 the most significant bit to change, 
    // set to 0 the least significant ones, and add the necessary 1 bits.
    w = (t + 1) | (((~t & -~t) - 1) >> (__builtin_ctz(v) + 1));  
    

    x86 CPU 固有的__builtin_ctz(v) GNU C 编译器返回尾随零的数量。如果您使用 Microsoft 的 x86 编译器,则内在函数是 _BitScanForward。它们都发出 bsf 指令,但等效的指令可能适用于其他架构。如果不是,则考虑使用前面提到的对连续零位进行计数的方法之一。

    这是另一个版本,由于它的除法运算符,它往往会变慢,但它 不需要计算尾随零。

    unsigned int t = (v | (v - 1)) + 1;  
    w = t | ((((t & -t) / (v & -v)) >> 1) - 1);  
    

    感谢阿根廷的 Dario Sneidermanis,他于 2009 年 11 月 28 日提供了此信息。

    【讨论】:

    【解决方案2】:

    对于 bit hack,我喜欢参考这个页面:Bit Twiddling Hacks

    关于您的具体问题,请阅读标题为 Compute the lexicographically next bit permutation 的部分。


    按字典顺序计算下一位排列

    假设我们在一个整数中有一个 N 位设置为 1 的模式,并且我们想要在字典学意义上的 N 1 位的下一个排列。例如,如果 N 为 3 且位模式为 00010011,则下一个模式将为 00010101、00010110、00011001、00011010、00011100、00100011 等。以下是计算下一个排列的快速方法。

    unsigned int v; // current permutation of bits 
    unsigned int w; // next permutation of bits
    
    unsigned int t = v | (v - 1); // t gets v's least significant 0 bits set to 1
    // Next set to 1 the most significant bit to change, 
    // set to 0 the least significant ones, and add the necessary 1 bits.
    w = (t + 1) | (((~t & -~t) - 1) >> (__builtin_ctz(v) + 1));  
    

    x86 CPU 固有的 __builtin_ctz(v) GNU C 编译器返回尾随零的数量。如果您使用的是 x86 的 Microsoft 编译器,则内在函数是 _BitScanForward。它们都发出 bsf 指令,但其他体系结构也可以使用等效指令。如果不是,则考虑使用前面提到的对连续零位进行计数的方法之一。 这是另一个版本,由于它的除法运算符,它往往会变慢,但它不需要计算尾随零。

    unsigned int t = (v | (v - 1)) + 1;  
    w = t | ((((t & -t) / (v & -v)) >> 1) - 1);  
    

    感谢阿根廷的 Dario Sneidermanis,他于 2009 年 11 月 28 日提供了此信息。

    【讨论】:

      【解决方案3】:

      添加到下面包含的@sehe 的答案(最初来自Dario Sneidermanis,也来自http://graphics.stanford.edu/~seander/bithacks.html#NextBitPermutation。)

      #include <utility>
      #include <iostream>
      #include <bitset>
      
      using I = uint8_t;
      
      auto dump(I v) { return std::bitset<sizeof(I) * __CHAR_BIT__>(v); }
      
      I bit_twiddle_permute(I v) {
          I t = v | (v - 1); // t gets v's least significant 0 bits set to 1
          // Next set to 1 the most significant bit to change, 
          // set to 0 the least significant ones, and add the necessary 1 bits.
          I w = (t + 1) | (((~t & -~t) - 1) >> (__builtin_ctz(v) + 1));  
          return w;
      }
      
      int main() {
          I p = 0b001001;
          std::cout << dump(p) << "\n";
          for (I n = bit_twiddle_permute(p); n>p; p = n, n = bit_twiddle_permute(p)) 
      {
              std::cout << dump(n) << "\n";
          }
      }
      

      bit_twiddle_permute(I v) 存在边界问题。每当 v 是最后一个排列时,t 都是 1(例如 2^8 - 1),(~t &amp; -~t) = 0,并且 w 是比 v 少一个 1 的位的第一个排列,除非v = 000000000 在这种情况下w = 01111111 .特别是如果您将 p 设置为 0; main 中的循环将产生具有七个 1 的所有排列,并且对 for 循环进行以下轻微修改,将循环遍历所有设置为 0、7、6、...、1 位的排列 -

      for (I n = bit_twiddle_permute(p); n>p; n = bit_twiddle_permute(n)) 
      

      如果这是意图,也许值得评论。如果不是,修复是微不足道的,例如

      if (t == (I)(-1)) { return v >> __builtin_ctz(v); }
      

      所以还有一点小小的简化

      I bit_twiddle_permute2(I v) {
          I t = (v | (v - 1)) + 1;
          if (t == 0) { return v >> __builtin_ctz(v); }
          I w = t | ((~t & v) >> (__builtin_ctz(v) + 1));
          return w;
      }
      
      int main() {
          I p = 0b1;
          cout <<  dump(p) << "\n";
          for (I n = bit_twiddle_permute2(p); n>p; n = bit_twiddle_permute2(n)) {
              cout << dump(n) << "\n";
          }
      }
      

      以下改编自达里奥·斯奈德曼尼斯的想法可能会更容易理解

      I bit_twiddle_permute3(I v) {
          int n = __builtin_ctz(v);
          I s = v >> n;  
          I t = s + 1;  
          I w = (t << n) | ((~t & s) >> 1);
          return w;
      }
      

      或者对我在本文开头提到的问题有类似的解决方案

      I bit_twiddle_permute3(I v) {
          int n = __builtin_ctz(v);
          I s = v >> n;  
          I t = s + 1;  
          if (v == 0 || t << n == 0) { return s; }
          I w = (t << n) | ((~t & s) >> 1);
          return w;
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-05-03
        • 2014-07-06
        • 2021-05-14
        • 1970-01-01
        相关资源
        最近更新 更多