c# - 最小长度子集的高效幂集算法

标签 c# linq powerset

我正在使用以下 C# 函数来获取限于最小长度子集的幂集

string[] PowerSet(int min_len, string set)
{
    IEnumerable<IEnumerable<string>> seed = 
                    new List<IEnumerable<string>>() { Enumerable.Empty<string>() };

    return set.Replace(" ", "")
              .Split(',')
              .Aggregate(seed, (a, b) => a.Concat(a.Select(x => x.Concat(new[] { b }))))
              .Where(subset => subset.Count() >= min_len)
              .Select(subset => string.Join(",", subset))
              .ToArray();
}

问题在于,当原始集很大时,即使最小长度也很大,算法也必须非常努力地工作。

例如:

    PowerSet(27, "1,11,12,17,22,127,128,135,240,254,277,284,292,296,399,309,322,326,333,439,440,442,447,567,580,590,692,697");

应该很容易,但是对于上面的功能来说太冗长了。我正在寻找可以有效处理这些情况的函数的简洁修改。

最佳答案

快速浏览一下您的方法,其中一个低效之处在于创建了每个可能的子集,而不管它是否有足够的成员来保证包含在有限的超集中。

考虑改为实现以下扩展方法。该方法可以根据数量裁剪掉一些不需要的子集,避免计算量过大。

public static List<List<T>> PowerSet<T>(List<T> startingSet, int minSubsetSize)
{
    List<List<T>> subsetList = new List<List<T>>();

    //The set bits of each intermediate value represent unique 
    //combinations from the startingSet.
    //We can start checking for combinations at (1<<minSubsetSize)-1 since
    //values less than that will not yield large enough subsets.
    int iLimit = 1 << startingSet.Count;
    for (int i = (1 << minSubsetSize)-1; i < iLimit; i++)
    {
        //Get the number of 1's in this 'i'
        int setBitCount = NumberOfSetBits(i);

        //Only include this subset if it will have at least minSubsetSize members.
        if (setBitCount >= minSubsetSize)
        {
            List<T> subset = new List<T>(setBitCount);

            for (int j = 0; j < startingSet.Count; j++)
            {
                //If the j'th bit in i is set, 
                //then add the j'th element of the startingSet to this subset.
                if ((i & (1 << j)) != 0)
                {
                    subset.Add(startingSet[j]);
                }
            }
            subsetList.Add(subset);
        }
    }
    return subsetList;
}

每个增量 i 中的集合位数告诉您子集中有多少成员。如果没有足够的设置位,那么创建由位组合表示的子集的工作就没有意义。 NumberOfSetBits 可以通过多种方式实现。参见 How to count the number of set bits in a 32-bit integer?各种方法、解释和引用。这是从该 SO 问题中提取的一个示例。

public static int NumberOfSetBits(int i)
{
    i = i - ((i >> 1) & 0x55555555);
    i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
    return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
}

现在,虽然此解决方案适用于您的示例,但我认为如果您将最小子集大小降低太多或继续增加 startingSet 的大小,您将遇到运行时间长和内存问题。如果您的问题中没有发布具体要求,我无法判断此解决方案是否适合您和/或对于您的预期输入案例范围是否安全。

如果您发现此解决方案仍然太慢,可以拆分操作以进行并行计算,或许可以使用 PLINQ 功能。

最后,如果你想用 LINQ 修饰扩展方法,它看起来像下面这样。但是,正如所写的那样,我认为如果不对其进行一些更改,您会看到性能变慢。

public static IEnumerable<List<T>> PowerSet<T>(List<T> startingSet, int minSubsetSize)
{
    var startingSetIndexes = Enumerable.Range(0, startingSet.Count).ToList();

    var candidates = Enumerable.Range((1 << minSubsetSize)-1, 1 << startingSet.Count)
                               .Where(p => NumberOfSetBits(p) >= minSubsetSize)
                               .ToList();

    foreach (int p in candidates)
    {
        yield return startingSetIndexes.Where(setInd => (p & (1 << setInd)) != 0)
                                       .Select(setInd => startingSet[setInd])
                                       .ToList();
    }
}

关于c# - 最小长度子集的高效幂集算法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9651987/

相关文章:

c# - 生成最短的字母数字保存代码

c# - x64 平台上调试器中奇怪的三元运算符行为

c# - 如何在同一个类的main方法之外引用一个对象?

c# - 压缩现有 XPS 文档

c# - 如何在C#中与列表相交?

c# - 如何使用c#审查字符串中的前10个字符

linq - 使用 LINQ 在子查询中高级多重联接

java - 你怎么能以最小的时间复杂度找到总和等于 k ​​的最长子集(powerset)的长度?

javascript - 这种组合生成递归有什么问题?

c# - 轮询 TFS 时 Jenkins 错误 - 无法执行获取操作,因为它是可写的