我会将其视为二进制算术问题:
static void Main(string[] args)
{
int[] arr = { 1, 2, 3 };
PickElements(0, arr);
}
static void PickElements<T>(int depth, T[] arr, int mask = -1)
{
int bits = Math.Min(32, arr.Length);
// keep just the bits from mask that are represented in arr
mask &= ~(-1 << bits);
if (mask == 0) return;
// UI: write the options
for (int i = 0; i < depth; i++ )
Console.Write('>'); // indent to depth
for (int i = 0; i < arr.Length; i++)
{
if ((mask & (1 << i)) != 0)
{
Console.Write(' ');
Console.Write(arr[i]);
}
}
Console.WriteLine();
// recurse, taking away one bit (naive and basic bit sweep)
for (int i = 0; i < bits; i++)
{
// try and subtract each bit separately; if it
// is different, recurse
var childMask = mask & ~(1 << i);
if (childMask != mask) PickElements(depth + 1, arr, childMask);
}
}
对于TreeView,只需将Console.Write 等替换为节点创建,大概将父节点传入(和向下)作为递归的一部分(可能代替depth)。
要查看它在做什么,请考虑二进制文件; -1 是:
11111111111111...111111111111111
然后我们查看bits,我们从数组长度派生出来,在这个例子中发现它是3。那么,我们只需要查看 3 位即可;行:
~(-1 << bits)
为此计算掩码,因为:
-1 = 1111111....1111111111111
(-1 << 3) = 1111111....1111111111000 (left-shift back-fills with 0)
~(-1 << 3) = 0000000....0000000000111 (binary inverse)
然后我们将其应用于我们的输入掩码,因此我们只通过mask &= ... 查看最低有效 3 位。如果结果是 0,我们已经没有事情可做,所以停止递归。
UI 更新很简单;我们只扫描我们关心的 3 位,检查当前位是否为我们的掩码“打开”; 1 << i 仅使用“第 i 个设置位”创建掩码; & 和 != 0 检查该位是否已设置。如果是,我们在输出中包含该元素。
最后,我们需要开始带走位,来查看子树;我们可以对此可能更复杂,但我选择只扫描所有位并尝试它们 - 最坏的情况是每个级别 32 位测试,这没什么。和以前一样,1 << i 创建了一个仅包含“第 i 个设置位”的掩码。这次我们想禁用那个位,所以我们通过mask & ~(...)“否定”和“与”。该位可能已经被禁用,因此childMask != mask 检查确保我们仅在禁用之前启用的位时实际上递归。
最终结果是我们最终得到了连续的掩码:
11..1111111111111111 (special case for first call; all set)
110 (first bit disabled)
100 (first and second bits disabled)
010 (first and third bits disabled)
101 (second bit disabled)
100 (second and first bits disabled)
001 (second and third bits disabled)
011 (third bit disabled)
010 (third and first bits disabled)
001 (third and second bits disabled)
请注意,对于更简单的组合示例,可以仅在单个for中迭代,使用位来选择元素;但是,我以递归方式完成了它,因为我们需要构建一个连续减法树,而不仅仅是没有特定顺序的平面可能性。