繁体   English   中英

在计算 n 的幂集时找到长度为 k 的所有子集?

[英]Find all subsets upto length k while calculating power set of n?

给定一个包含 n 个元素的集合 {1, 2, 3, 4, 5 ... n},我们需要找到长度不超过 k 的所有子集。

例如,

输入: n = 4 and k = 2

输出: {{1}, {2}, {3}, {4}, {1, 2}, {1, 3}, {1, 4}, {2, 3}, {2, 4}, {3, 4}}

private static final List<Set<Set<Integer>>> innerPowerSets = new ArrayList<>();

public Set<Set<Integer>> powerSet(final int numberOfItems, final int maximumSubsetLength, final List<Set<Set<Integer>>> innerPowerSets) {
        if (innerPowerSets.isEmpty()) {
            innerPowerSets.add(new HashSet<>());
            innerPowerSets.get(0).add(new HashSet<>());
        } else {
            log.info("Saved Power Sets: " + innerPowerSets.size());
        }

        final int[] missingPowerSets;

        if (numberOfItems+1 > innerPowerSets.size()) {
            missingPowerSets = IntStream.range(innerPowerSets.size(), numberOfItems+1).toArray();
        } else {
            return innerPowerSets.get(numberOfItems);
        }

        for (final Integer item : missingPowerSets) {
            final Set<Set<Integer>> previousPowerSet = innerPowerSets.get(innerPowerSets.size() - 1);
            final Set<Set<Integer>> temp = new HashSet<>(previousPowerSet);
            for (Set<Integer> innerSet : previousPowerSet) {
                innerSet = new HashSet<>(innerSet);
                if (innerSet.size() < maximumSubsetLength) {
                    innerSet.add(item);
                    temp.add(innerSet);
                }
            }
            innerPowerSets.add(new HashSet<>(temp));
        }
        return innerPowerSets.get(innerPowerSets.size()-1);
    }

上面的代码是带有记忆化的迭代模式,原因是我需要多次调用它并且不想浪费时间一次又一次地计算相同的子集。

问题:我有一个对象列表,我需要长度不超过 k 的子集。 我用上面的代码来获取索引的子集,然后直接使用这个indices_subset来获取Object_subsets。 存储索引子集有助于我将其应用于任何长度的对象列表。 但问题是,它花费了太多时间。 如果我删除记忆并直接应用长度为 k 的幂集计算,它是相当快的。

如果需要更多信息,请发表评论。

使用迭代模式计算器将直接对象功率设置为长度 k:

public static <T> List<List<T>> powerSet(final List<T> originalSet, final int subsetLengthOf) {
   final List<List<T>> result = new ArrayList<>();
   result.add(new ArrayList<>());
   for (final T item : originalSet) {
      final List<List<T>> temp = new ArrayList<>();
      for (List<T> innerSet : result) {
         innerSet = new ArrayList<>(innerSet);
         if (innerSet.size() < subsetLengthOf) {
            innerSet.add(item);
            temp.add(innerSet);
         }
      }
      result.addAll(temp);
   }
   return result;
}

您并没有真正获得记忆的好处,因为您声称您的第一种方法正在这样做。

记忆作为一个概念:

Memoization 是一种避免重新计算相同问题(相同输入)的方法。 您可以通过简单地存储结果(针对特定输入)来避免重新计算,并且当给出相同的输入时,您无需重新计算,只需在数组中查找并返回结果。 这是您使用备忘录所做的主要节省。

请注意,您重复使用前一个子问题的解决方案越多,您获得的性能优势就越多。 在您的第一种方法中,您不会重复使用太多。 (您只是重新使用前一个子问题的解决方案 - 并创建一个新的解决方案)


让我们将问题定义为P(numberOfItems, maximumSubsetLength)

您正在尝试使用P(numberOfItems, maximumSubsetLength - 1)查找P(numberOfItems, maximumSubsetLength) P(numberOfItems, maximumSubsetLength - 1) 这两种方法都是如此。 唯一的区别是,在您的第一种方法中,您将所有结果(不改变它们)存储在innerPowerSets

如果您仔细注意到,您只是在使用previousPowerSet而没有使用来自innerPowerSets final Set<Set<Integer>> previousPowerSet = innerPowerSets.get(innerPowerSets.size() - 1); 这正是您的第二种方法正在做的事情。 唯一的区别是它没有存储每个子问题的结果。 它只是使用前一个子问题的解决方案并对其进行变异以获得下一个子问题的解决方案。 因此,您正在获得性能改进。


Crux:您重复使用的次数越多,您获得的性能优势就越多。 您的两种方法都只是使用前一个子问题的解决方案(没有别的)。 第一个解决方案(您声称它是记忆化的)正在做额外的工作来创建现有解决方案的副本,然后对其进行变异以获得当前解决方案。 第二种迭代方法是简单地改变前一个解决方案以获得当前子问题的解决方案(没有任何额外的复制工作——因为它只是改变了前一个解决方案)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM