按格雷码顺序遍历所有组合
Iterate through all combinations in Gray code order
假设我在一个数组a中有n整数,我想迭代这些整数的所有可能子集,找到和,然后用它做点什么。
我立即做的是创建一个位字段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 = ???;
}
但我不知道如何直接有效地计算哪个BitToFlip。所需的值基本上是序列A007814。我知道我可以使用公式(k>>1)^k
计算格雷码,并将其与前一个进行异或,但随后我需要找到更改的比特的位置,这可能不会快得多。
那么,有没有比每次重新计算整个和(最多64个值)更快的更好的方法来确定这些值(翻转位的索引),最好没有周期?
要将位掩码转换为位索引,可以使用ffs
函数(如果有),它对应于某些机器上的机器操作码。
否则,格雷码中更改的位对应标尺功能:
0, 1, 0, 2, 0, 1, 0, 3, 0, 1...
其中有一个简单的递归。您可以用堆栈模拟递归(它的最大深度为O(log N),所以空间不大),但ffs
可能要快得多。
(顺便说一句,即使你从右到左一次计数一个比特,增量函数平均为O(1)
,因为从1到2k的整数中尾随0的总数是2k-1。)
所以我想出了这个:
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);
}
它运行得非常好,将执行时间(与总是重新计算整个总和相比)从254秒降低到只有7秒,n=32。我还发现,由于rici提到的原因,使用for循环计算尾随零只比使用_BitScanForward64
慢一点(~15%)。所以谢谢。
相关文章:
- 有什么方法可以遍历结构吗
- 在循环中按顺序遍历成员变量
- 遍历模板参数
- 在遍历处理程序的向量时注册和注销处理程序
- C++RapidXml-使用first_node()遍历以修改XML文件中节点的值
- 遍历并行数组以确定C++中的最大数字
- 遍历顺序由 std::文件系统directory_iterator给出
- 遍历链表时的无限循环
- 遍历unordered_map向量
- 从预序遍历构造 bst 的 c++ 和 python 解决方案之间的区别
- C++声明双链表,使用两个 for 循环双向遍历列表并打印
- 如何正确地推回然后遍历堆中对象的向量?
- 遍历二维数组的所有子数组
- 如何在可变参数模板函数中遍历可变参数元组?
- 避免在遍历 IShellItemArray 时出现代码重复
- 如何遍历几个每小时一次的根(.root)文件,并将它们组合成更大的每日数据.root文件?
- 遍历所有可能的组合
- 遍历所有布尔组合
- 按格雷码顺序遍历所有组合
- 输出或遍历集合的所有对组合的标准算法