假设我在数组 a 中有 n 整数,我想迭代这些整数的所有可能子集,找到 sum ,然后用它做点什么。
我立即做的是创建一个位域 b ,它指示哪些数字包含在子集中,并使用++b
迭代其可能的值。然后,为了计算每一步中的总和,我不得不迭代所有这些位:
int sum = 0;
for (int i = 0; i < n; i++)
if (b&1<<i)
sum += a[i];
然后我意识到,如果我以格雷码顺序迭代 b 的可能值,那么每次只翻转一个比特,我就不必完全重建总和,但只需要添加或减去从子集中添加或删除的单个值。它应该像这样工作:
int sum = 0;
int whichBitToFlip = 0;
bool isBitSet = false;
for (int k = 0; whichBitToFlip < n; k++) {
sum += (isBitSet ? -1 : 1)*a[whichBitToFlip];
// do something with sum here
whichBitToFlip = ???;
bool isBitSet = ???;
}
但我无法弄清楚如何直接有效地计算 whichBitToFlip 。期望的值基本上是序列A007814。我知道我可以使用公式(k>>1)^k
计算格雷码并使用前一个计算格雷码,但是我需要找到更改位的位置,这可能不会更快。
那么有没有更好的方法来确定这些值(翻转位的索引),最好是没有一个循环,比每次重新计算总和(最多64个值)更快?
答案 0 :(得分:1)
要将位掩码转换为位索引,可以使用ffs
函数(如果有的话),这对应于某些机器上的机器操作码。
否则,格雷码中的位改变对应于标尺函数:
0, 1, 0, 2, 0, 1, 0, 3, 0, 1...
有一个简单的递归。您可以使用堆栈模拟递归(它将具有最大深度O(log N),因此它的空间不大),但可能ffs
可能要快得多。
(顺便说一句,即使你从右到左一次计数一位,增量函数平均为O(1)
,因为整数中的尾随0的总数从1开始到2 k 是2 k -1。)
答案 1 :(得分:0)
所以我想出了这个:
int sum = 0;
unsigned long grayPos = 0;
int graySign = 1;
for (uint64 k = 2; grayPos < n; k++) {
sum += graySign*a[grayPos];
// Do something with sum
#ifdef _M_X64
grayPos = n;
_BitScanForward64(&grayPos, k);
#else
for (grayPos = 0; !(k&1ull<<grayPos); grayPos++);
#endif
graySign = 2-(k>>grayPos&0x3);
}
它的效果非常好,将执行时间(与总是重新计算总和相比)从 n = 32降低到254秒。我还发现用尾数计算尾随零由于rici提到的原因,周期仅比使用_BitScanForward64
稍微(~15%)。谢谢。