65.9K
CodeProject 正在变化。 阅读更多。
Home

生成子集

starIconstarIconemptyStarIconemptyStarIconemptyStarIcon

2.00/5 (1投票)

2018 年 9 月 24 日

CPOL

2分钟阅读

viewsIcon

6424

如何生成子集

时不时地,我会被涉及从给定对象集合中寻找最优子集的问题所挑战。几周前又发生了类似的情况。这个问题类似于背包问题(寻找总大小受限的物品中最有利的子集),而且……当然,我忘记了如何使用动态规划来解决它。但是,很多年前,我学到了一种非常简单的(容易记忆的)算法,它救了我一命,我将在本文中描述它。

算法设计手册》,第453页将其定义为二进制计数。其背后的思想是 0 到 2n-1(总数为 2n)之间的数字与长度为 n 的二进制字符串(总数为 2n)之间的一一映射,其中索引 i 处的位 1 表示“考虑原始集合中索引 i 处的对象,用于给定的子集”。我们还知道一个大小为 n 的集合的子集总是 2n。到处都是 2n,太棒了!

让我们通过一个简单的例子来实际操作一下,一个小的集合 {A, B, C},其中 n=3。我们对空集不感兴趣,因此我们将忽略第 0 次迭代,剩下总共 23-1=7 个子集

迭代 二进制字符串 要考虑的对象的索引 子集
1 001 1 {A}
2 010 2 {B}
3 011 1 和 2 {A, B}
4 100 3 {C}
5 101 1 和 3 {A, C}
6 110 2 和 3 {B, C}
7 111 1, 2 和 3 {A, B, C}

我需要强调的是,这是一个指数级的(或 O(2n))复杂度算法。它对于大型集合来说效率非常低。这里是一个代码示例

import java.util.LinkedHashSet;
import java.util.Set;

public class AllSubSets {
 public static void main(String args[]) {
  String[] setValues = new String[] { "A", "B", "C", "D", "E",
    "F", "G", "H", "I", "J" };

  long limit = 1 << setValues.length; // this is 2^length

  for (long l = 1; l < limit; l++) {
   Set<string> subSet = new LinkedHashSet<>();
   for (int i = 0; i < setValues.length; i++) {
    if ((l & (1 << i)) > 0) {
     subSet.add(setValues[i]);
    }
   }
   subSet.forEach(System.out::print);
   System.out.println();
  }
 }
}

以及结果

A
B
AB
C
AC
BC
ABC
D
... (trimmed due to the size) ...
CDEFGHIJ
ACDEFGHIJ
BCDEFGHIJ
ABCDEFGHIJ

细心的读者会立即回复说,这个版本仅限于最多 64 个对象的集合(在 Java 8 中,long限制为 64 位,可以被视为无符号的)。BigInteger 支持按位运算,解决了这个限制。是的,额外的子集构造循环使其成为 O(n2n) 算法,但它仍然是指数级的。

然而,正如前面提到的,这并不是一个高效的算法。任何大于 64 个对象的集合都会产生超过 264 个子集,这是一个很大的数字。即使是 232 尺寸也相当可观,所以不要将此算法用于大型集合。

© . All rights reserved.