根据我碰巧遇到的一些代码,它与 geeksforgeeks 解决方案非常相似(请参阅此答案:https://stackoverflow.com/a/14717440/1566221)和高度优化的 @QuestionC's answer 版本,它避免了一些变化,我得出结论:除法在某些 CPU(即,在我的 Intel i5 笔记本电脑上)上足够慢,以至于循环实际上胜出。
但是,可以用移位循环替换 g-for-g 解决方案中的除法,结果证明这是最快的算法,同样只是在我的机器上。我将代码粘贴在这里,供任何想测试它的人使用。
对于任何实现,都有两种烦人的极端情况:一种是给定整数为 0;另一种是给定整数为 0。另一个是整数是可能的最大值。以下函数都具有相同的行为:如果给定具有 k 位的最大整数,则它们返回具有 k 位的最小整数,从而重新启动循环。 (这也适用于 0:这意味着给定 0,函数返回 0。)
带除法的 Bit-hack 解决方案:
template<typename UnsignedInteger>
UnsignedInteger next_combination_1(UnsignedInteger comb) {
UnsignedInteger last_one = comb & -comb;
UnsignedInteger last_zero = (comb + last_one) &~ comb;
if (last_zero)
return comb + last_one + ((last_zero / last_one) >> 1) - 1;
else if (last_one)
return UnsignedInteger(-1) / last_one;
else
return 0;
}
用移位循环代替除法的 Bit-hack 解决方案
template<typename UnsignedInteger>
UnsignedInteger next_combination_2(UnsignedInteger comb) {
UnsignedInteger last_one = comb & -comb;
UnsignedInteger last_zero = (comb + last_one) &~ comb;
UnsignedInteger ones = (last_zero - 1) & ~(last_one - 1);
if (ones) while (!(ones & 1)) ones >>= 1;
comb += last_one;
if (comb) comb += ones >> 1; else comb = ones;
return comb;
}
优化换档方案
template<typename UnsignedInteger>
UnsignedInteger next_combination_3(UnsignedInteger comb) {
if (comb) {
// Shift the trailing zeros, keeping a count.
int zeros = 0; for (; !(comb & 1); comb >>= 1, ++zeros);
// Adding one at this point turns all the trailing ones into
// trailing zeros, and also changes the 0 before them into a 1.
// In effect, this is steps 3, 4 and 5 of QuestionC's solution,
// without actually shifting the 1s.
UnsignedInteger res = comb + 1U;
// We need to put some ones back on the end of the value.
// The ones to put back are precisely the ones which were at
// the end of the value before we added 1, except we want to
// put back one less (because the 1 we added counts). We get
// the old trailing ones with a bit-hack.
UnsignedInteger ones = comb &~ res;
// Now, we finish shifting the result back to the left
res <<= zeros;
// And we add the trailing ones. If res is 0 at this point,
// we started with the largest value, and ones is the smallest
// value.
if (res) res += ones >> 1;
else res = ones;
comb = res;
}
return comb;
}
(有人会说上面是另一个小技巧,我不会争论。)
高度不具代表性的基准
我通过运行所有 32 位数字对此进行了测试。 (也就是说,我用 i 创建了最小的模式,然后循环遍历所有的可能性,对于 i 的每个值从 0 到 32。):
#include <iostream>
int main(int argc, char** argv) {
uint64_t count = 0;
for (int i = 0; i <= 32; ++i) {
unsigned comb = (1ULL << i) - 1;
unsigned start = comb;
do {
comb = next_combination_x(comb);
++count;
} while (comb != start);
}
std::cout << "Found " << count << " combinations; expected " << (1ULL << 32) << '\n';
return 0;
}
结果:
1. Bit-hack with division: 43.6 seconds
2. Bit-hack with shifting: 15.5 seconds
3. Shifting algorithm: 19.0 seconds