geeksforgeeks网站上提供了此问题的解决方案。
我想知道是否存在更好,更简单的解决方案?理解这有点复杂。只需一个算法就可以了。
答案 0 :(得分:2)
有一个更简单但效率更低的一个。它如下:
当然效率极低。考虑一个2的幂(设置为1位)的数字。您必须将此数字加倍才能得到答案,在每次迭代中将数字增加1。当然它不会起作用。
如果您想要一个更简单的高效算法,我不认为有一个。事实上,对我来说,它看起来非常简单明了。
编辑:通过"更简单",我的意思是它可以直接实现,并且代码行可能会少一些。
答案 1 :(得分:2)
我非常确定这种算法与链接算法一样高效且易于理解。
这里的策略是要明白,在不增加1的数量的情况下使数字更大的唯一方法是携带1,但如果你携带多个1,那么你必须将它们添加回来英寸
给定一个数字1001 1100
右移它直到值为奇数0010 0111
。请记住班次数:shifts = 2;
右移它直到值为偶数0000 0100
。记住执行的班次次数和消耗的比特数。 shifts += 3; bits = 3;
到目前为止,我们已经从算法中取了5个移位和3个比特来携带最低的数字。现在我们还钱了。
最右边的位1. 0000 0101
。我们现在欠它2位。 bits -= 1
左移3次以添加0' s。 0010 1000
。我们这样做了三次,因为shifts - bits == 3
shifts -= 3
现在我们欠了两位和两位。所以将它向左移动两次,每次将最左边的位设置为1。 1010 0011
。我们已经偿还了所有的比特和所有轮班。 bits -= 2; shifts -= 2; bits == 0; shifts == 0
以下是其他一些示例......每个步骤都显示为current_val, shifts_owed, bits_owed
0000 0110
0000 0110, 0, 0 # Start
0000 0011, 1, 0 # Shift right till odd
0000 0000, 3, 2 # Shift right till even
0000 0001, 3, 1 # Set LSB
0000 0100, 1, 1 # Shift left 0's
0000 1001, 0, 0 # Shift left 1's
0011 0011
0011 0011, 0, 0 # Start
0011 0011, 0, 0 # Shift right till odd
0000 1100, 2, 2 # Shift right till even
0000 1101, 2, 1 # Set LSB
0001 1010, 1, 1 # Shift left 0's
0011 0101, 0, 0 # Shift left 1's
答案 2 :(得分:2)
基于一些代码,我碰巧开始使用,这与geeksforgeeks解决方案非常相似(请参阅此答案:https://stackoverflow.com/a/14717440/1566221)和高度优化的@QuestionC's answer版本,避免了一些转变,我得出的结论是,某些CPU(即我的英特尔i5笔记本电脑上)的分区速度足够慢,但循环实际上胜出了。
但是,可以用移位循环替换g-for-g解决方案中的划分,结果证明这是最快的算法,仅在我的机器上。我在这里为任何想要测试它的人粘贴代码。
对于任何实现,有两个恼人的极端情况:一个是给定整数为0的位置;另一个是整数是最大可能值的位置。以下函数都具有相同的行为:如果给定具有k位的最大整数,则它们返回具有k位的最小整数,从而重新启动循环。 (也适用于0:它表示给定0,函数返回0。)
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;
}
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