关于C语言子集位向量生成算法循环逻辑的疑问
关于枚举子集位向量的C代码逻辑疑问解答
问题描述
我目前正在学习查询优化课程,接触到一个计算给定关系集合所有连接子集成本的算法,其中包含一个枚举所有子集的子程序。教材中提到一段可高效生成子集位向量的C语言代码片段:
S1 = S & -S; do { /* do something with subset S1 */ S1 = S & (S1 - S); } while (S1 != S);
其中S代表输入集合,S1遍历S的所有非空且非自身的子集。但我无法理解这段代码:当计算S与S的集合差时得到空集,循环中S1会一直是空集且永不终止,我忽略了什么?
问题分析与解答
你发现的问题是对的,这段代码存在逻辑缺陷,大概率是教材排版或笔误导致的。结合C语言整数的补码表示(尤其是无符号整数的溢出行为),我们用具体数值验证就能理清问题:
1. 补码运算的细节
在C语言中,无符号整数的减法溢出会触发模2^N运算,比如0 - S会得到UINT_MAX - S + 1(全1位模式减去S再加1),这和有符号整数的补码负数表现一致。但即便考虑这个特性,代码也不符合教材描述的遍历目标:
- 若S为
0b1010(十进制10):- 初始
S1 = S & -S = 0b10(2) - 处理S1后,计算
S1 - S = 2 - 10 = -8,无符号模式下为0b11111000,与S按位与得到0b1000(8) - 处理8后,计算
8-10=-2,无符号模式下为0b11111110,与S按位与得到0b1010(即S自身) - 此时判断
S1 != S不成立,循环终止。但过程中遍历到了S自身,违背“非自身”的要求。
- 初始
- 若S为单一位(比如
0b100,十进制4):- 初始
S1=4 - 处理后计算
4-4=0,S&0=0 - 判断
0 !=4成立,进入下一轮:计算0-4得到无符号的超大值,与S按位与回到4 - 此时判断
4 !=4不成立,循环终止。但过程中遍历到了空集0,违背“非空”的要求。
- 初始
2. 正确的非空真子集枚举代码
如果要实现遍历所有非空且非自身的子集,可以用以下经典写法(从最大的真子集开始倒序遍历):
unsigned int S1 = (S - 1) & S; // 初始化最大的真子集 while (S1 != 0) { // 处理子集S1的逻辑 S1 = (S1 - 1) & S; }
或者从最小的非空子集开始正序遍历:
unsigned int mask = S & -S; unsigned int S1 = mask; while (S1 != S) { // 处理子集S1的逻辑 mask = (S1 - S) & S; if (mask == 0) break; S1 = mask; }
总结
你并没有忽略任何细节,这段教材代码确实存在逻辑问题,无法准确遍历所有非空且非自身的子集,甚至在某些场景下会出现不符合预期的遍历结果。
内容的提问来源于stack exchange,提问作者nick_dataFE
相关产品推荐
相关产品推荐

