【发布时间】:2011-11-26 22:00:20
【问题描述】:
我忘了一点技巧来生成所有具有给定数量 1 的整数。有人记得吗(可能也可以解释一下)?
【问题讨论】:
标签: bit combinatorics
我忘了一点技巧来生成所有具有给定数量 1 的整数。有人记得吗(可能也可以解释一下)?
【问题讨论】:
标签: bit combinatorics
更新测试程序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 日提供了此信息。
【讨论】:
w = v + (v & -v)?
对于 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 日提供了此信息。
【讨论】:
添加到下面包含的@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 的位的第一个排列,除非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;
}
【讨论】: