比特黑客生成给定数量为1的所有整数

时间:2011-11-26 22:00:20

标签: bit combinatorics

我忘了有点黑客生成给定数量的1的所有整数。有没有人记得它(也可能解释一下)?

3 个答案:

答案 0 :(得分:18)

来自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编译器内在函数返回尾随零的数量。如果您使用的是x86的Microsoft编译器,则内在函数为_BitScanForward。这些都发出bsf指令,但是其他架构可以使用等价物。如果不是,则考虑使用其中一种方法来计算前面提到的连续零位。

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

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

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

答案 1 :(得分:11)

对于有点黑客攻击,我想参考这个页面: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日提供了此服务。

答案 2 :(得分:2)

添加到@ 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 & -~t) = 0,并且w是第一个比特排列,比v少一个1,除非{{1在这种情况下v = 000000000。特别是如果你将p设置为0; main中的循环将产生七个1的所有排列,并且for循环的以下略微修改将循环遍历所有排列,设置为0,7,6,...,1位 -

w = 01111111

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

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

因此需要额外的小简化

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

Dario Sneidermanis的想法的以下改编可能会更容易理解

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