经典重访: 如何按序生成集合的所有子集
如何列举一个集合的所有子集呢? 可能最先想到的是模拟二进制从0累加到111….111, 表示一个元素是否出现在子集中吧. 但是这种方法生成的子集序列的规律不那么明显, 如果要求生成的子集序列拥有一些良好的性质, 该怎么生成呢?
1. Gray Code
如果要求生成的子集序列相邻两个子集之间只相差一个元素, 那么这种序列对应的二进制编码就是gray编码. 例如含有两个元素的集合A={a, b}, 按照这个方法就应该生成{}, {a}, {a, b}, {b}, 这样的子集序列. 对应的二进制编码为00, 10, 11, 01.
gray编码构造的方法很巧妙. 将序列编号为0到2^n-1. 第一个子集为空集, 对应00…00. 以后, 如果前一个子集编号为偶数, 就在对应编码的第一位改变值(0->1, 1->0). 如果前一个子集编号为奇数, 那么就在第一个1之后的位置改变对应的值. 很容易想象这个方法是如何生成gray编码的. 假设此方法能够生成长度为k-1的gray编码, 那么在长度为k的情况下, 前一半的最后一位显然是0, 且最后一个编码为00..010, 且此编码的编号为奇数. 这样, 下一个编码为00..011, 之后的序列也容易想象和前一半序列正好镜像生成. 这种构造方法来自于对n维正方体的顶点用gray code进行编码.
0000 1000
1100 0100
0110 1110
1010 0010
0011 1011
1111 0111
0101 1101
1001 0001
2. Lexicographical Order
生成的子集按照字典顺序排列, 这也是常用的排列顺序. 而且生成的方法也很简单. 这里既然要以字典的顺序, 就应该认为集合中的元素是有全序的. 我们从空集开始, 每次在最后一个元素后面加上仅大于它的最小元素作为下一个子集. 如果不存在这样的元素, 我们删除这个最后元素, 将前一个元素增加为它的后继, 然后作为下一个子集返回.
-
a
ab
abc
ac
b
bc
c
3. Banker’s Sequence
以上两种子集顺序中, 子集的大小都不是递增的. 如果要求得到满足某个性质的最小的子集, 那么用以上两种方法都会在得到答案前枚举很多没有用的, 比较大的子集. 在<<Efficiently Enumerating the Subsets of a Set>>这篇文章中, 作者提出了将子集按照元素个数进行枚举的算法. 其实生成的方法非常简单, 简单地说, 就是将最后一个能够向右移位的1向右移, 如果不存在这样的位, 就在首位增加1. 然后将改变的位之后的所有1连续排列在这个位之后.
0000
1000
0100
0010
0001
1100
1010
1001
0110
0101
0011
1110
1101
1011
0111
1111
这种方法是有状态的, 在这篇博客里提到了一种无状态生成banker’s sequence的方法. 要生成第i个大小为k的, n元素集合的子集, 由于前C(n-1, k-1)个元素都是以1开始的, 如果i大于C(n-1, k-1), 那么第一个元素就没有出现在子集中, 否则它出现在这个子集中. 接着可以递归生成第i – C(n-1, k-1) (或者仍然i), 大小为k-1(或者仍然k), n-1元素集合的子集. 这种方法的效率是线性的, 并且不需要如上面的方法逐个生成子集.
本文介绍了三种生成集合子集的方法:GrayCode编码确保相邻子集只相差一个元素;LexicographicalOrder按照字典顺序排列子集;Banker’sSequence则按子集大小排序。每种方法都有其独特之处及适用场景。

394

被折叠的 条评论
为什么被折叠?



