如何生成给定大小的所有子集?

6

给定一个数字 n 和一个子集大小,我想要得到集合 {1, ..., n} 中指定大小的所有可能子集。

n = 5,subsetSize = 4 时的预期结果为:

{{1,2,3,4}, {1,2,3,5}, {1,3,4,5}, {1,2,4,5}, {2,3,4,5}}

我需要获取一个List<List<int>>,也就是说我需要获取(Newton符号)中的(subsetSize choose n)子集。

你有任何算法可以帮我找到这样一个整数列表的列表吗?如果有的话,我将在C#中实现它。


3
你卡在哪里了?我在你的帖子中没有看到任何代码。 - xxbbcc
这只是一个微不足道的“分而治之”,不是吗? - zerkms
1
可能是生成通用列表的组合的重复问题。 - Sphinxxx
有趣的问题,但似乎更适合作为编程挑战在codegolf.stackexchange.com上进行。 - Leon Newswanger
2
可能是列出字符串/整数的所有排列方式的重复问题。 - CSharpie
2
@CSharpie 不是那个(排列组合与排列)的重复。 - Bernhard Barker
5个回答

2
internal class Program
{
    private static IEnumerable<IEnumerable<int>> Subsets(int n, int subsetSize)
    {
        IEnumerable<int> sequence = Enumerable.Range(1, n);

        // generate list of sequences containing only 1 element e.g. {1}, {2}, ...
        var oneElemSequences = sequence.Select(x => new[] { x }).ToList();

        // generate List of int sequences
        var result = new List<List<int>>();
        // add initial empty set
        result.Add(new List<int>());

        // generate powerset, but skip sequences that are too long
        foreach (var oneElemSequence in oneElemSequences)
        {
            int length = result.Count;

            for (int i = 0; i < length; i++)
            {
                if (result[i].Count >= subsetSize)
                    continue;

                result.Add(result[i].Concat(oneElemSequence).ToList());
            }
        }

        return result.Where(x => x.Count == subsetSize);
    }

    private static void OutputSubset(int n, IEnumerable<IEnumerable<int>> subsets)
    {
        Console.WriteLine("n: {0}", n);
        foreach (var subset in subsets)
        {
            Console.WriteLine("\t{0}", string.Join(" ", subset.Select(x => x.ToString())));
        }
    }

    private static void Main()
    {
        for (int n = 1; n < 500; n++)
        {
            var subsets = Subsets(n, subsetSize: 4);
            OutputSubset(n, subsets);
        }
    }
}

输出:
n: 1
n: 2
n: 3
n: 4
        1 2 3 4
n: 5
        1 2 3 4
        1 2 3 5
        1 2 4 5
        1 3 4 5
        2 3 4 5
n: 6
        1 2 3 4
        1 2 3 5
        1 2 4 5
        1 3 4 5
        2 3 4 5
        1 2 3 6
        1 2 4 6
        1 3 4 6
        2 3 4 6
        1 2 5 6
        1 3 5 6
        2 3 5 6
        1 4 5 6
        2 4 5 6
        3 4 5 6
n: 7
        1 2 3 4
        1 2 3 5
        1 2 4 5
        1 3 4 5
        2 3 4 5
        1 2 3 6
        ...

1
你可以使用位掩码算法。这个逻辑检查子集中元素的数量、每个唯一子集的总和并对结果进行排序。这个算法很基础,可以优化以提高执行速度并去除不必要的条件。
class Program
{
    static void Main(string[] args)
    {
        int number, cntOfElements;
        int.TryParse(Console.ReadLine(), out number);
        int.TryParse(Console.ReadLine(), out cntOfElements);
        int[] intArray = Regex.Split(Console.ReadLine(), @"\s+").Select(int.Parse).Distinct().ToArray();
        bool hasMatchingSubs = false;
        //The amount of possible combinations are 2 of power - the count of integers in the array
        //if they (the numbers) are 3 the combinations with the zero are 8 (in binary 0000 1000)
        //so we have 3 numbers and the bits before the setted bit of the 8 are the same count
        //this means that we can use them to get all the unique combinations for that amount of numbers 
        //7 combinations without the zero (in binary 0000 0111).
        int combos = (int)Math.Pow(2, intArray.Length);
        List<List<int>> result = new List<List<int>>();

        //we cicle around all the possible combinations
        for (int mask = 1; mask < combos; mask++)
        {
            int sum = 0;
            List<int> list = new List<int>();
            //In the second for construction when the corresponding bit is set wi add this value
            //to the sum for this combination and when the bit is 0 we skip the adding
            for (int idx = 0; idx < intArray.Length; idx++)
            {
                if ((mask >> idx & 1) == 1)
                {
                    sum += intArray[(intArray.Length - 1) - idx];
                    list.Add(intArray[(intArray.Length - 1) - idx]);
                }
            }

            //We are checking the sum for this combination before the next one
            if (sum == number && list.Count() == cntOfElements)
            {
                hasMatchingSubs = true;
                result.Add(list);
            }
        }

        if (!hasMatchingSubs)
        {
            Console.WriteLine("No matching subsets.");
        }
        else
        {
            result.ForEach(p => p.Sort()); //Sorting all elements in the inner lists
            result = result.OrderBy(a => a.Count).ThenBy(b => b.First()).ToList();  //ordering by the count of items in each list
                                                                                    //and than by the value of the sirst integer
            result.ForEach(p => Console.WriteLine("{0} = {1}", string.Join(" + ", p), number));
        }
    }
}

0
今天我需要这个(我的以前的解决方案效率太低了)。它比幂集方法更快。它返回一个 IEnumerable,所以如果你不需要一次性将它们全部存储在内存中,这应该相对节省内存。以下是我想出来的代码:
public class Subsets
{
    public static IEnumerable<IList<int>> Compute(int countOfSet, int countOfSubset)
    {
        if (countOfSet < 0)
        {
            throw new ArgumentException(@"countOfSet must be at least 0");
        }

        if (countOfSubset < 0)
        {
            throw new ArgumentException(@"countOfSet must be at least 0");
        }

        if (countOfSubset > countOfSet)
        {
            throw new ArgumentException(@"countOfSubset must less than or equal to countOfSet");
        }

        //var allChoices = Enumerable.Range(0, countOfSet);
        //var result = GenerateOld(countOfSubset, allChoices, Enumerable.Empty<int>()).ToArray();
        var result = Generate(countOfSet, countOfSubset);

        return result;
    }

    public static IEnumerable<IList<int>> Generate(int countOfSet, int countOfSubset)
    {
        // If countOfSet is 5 and countofSubset is 3, maxCounterValue will be 234
        var maxCounterValue = Enumerable.Range(0, countOfSet)
            .Reverse()
            .Take(countOfSubset)
            .Reverse()
            .ToArray();

        var counters = new int[countOfSubset];

        // Initialize counters to 0,1,2,3,...
        foreach (var i in Enumerable.Range(0, countOfSubset))
        {
            counters[i] = i;
        }

        while (true)
        {
            yield return counters.ToArray();

            if (counters.SequenceEqual(maxCounterValue))
            {
                break;
            }

            var currentCounter = countOfSubset - 1;

            counters[currentCounter]++;

            // In case there was overflow, increment previous counters.
            while (counters[currentCounter] > maxCounterValue[currentCounter])
            {
                currentCounter--;
                counters[currentCounter]++;
            }
            // "Reset" counters on the way back up.
            while (currentCounter < countOfSubset - 1)
            {
                counters[currentCounter + 1] = counters[currentCounter] + 1;
                currentCounter++;
            }
        }
    }

这是我的测试(NUnit 3):

using NUnit.Framework;
using System.Linq;

//[Timeout(1000)]
public class SubsetsTests
{
    [Test]
    public void Compute0_1()
    {
        var act = new TestDelegate(() => Subsets.Compute(0, 1).ToArray());
        Assert.That(act, Throws.Exception);
    }

    [Test]
    public void Compute_1_0()
    {
        var act = new TestDelegate(() => Subsets.Compute(-1, 0).ToArray());
        Assert.That(act, Throws.Exception);
    }

    [Test]
    public void Compute1__1()
    {
        var act = new TestDelegate(() => Subsets.Compute(1, -1).ToArray());
        Assert.That(act, Throws.Exception);
    }

    [Test]
    public void Compute0_0()
    {
        var result = Subsets.Compute(0, 0).ToArray();
        Assert.That(result.Length, Is.EqualTo(1));
        Assert.That(result[0], Is.EquivalentTo(new int[0]));
    }

    [Test]
    public void Compute1_1()
    {
        var result = Subsets.Compute(1, 1).ToArray();
        Assert.That(result.Length, Is.EqualTo(1));
        Assert.That(result[0], Is.EquivalentTo(new[] { 0 }));
    }

    [Test]
    public void Compute1_0()
    {
        var result = Subsets.Compute(1, 0).ToArray();
        Assert.That(result.Length, Is.EqualTo(1));
        Assert.That(result[0], Is.EquivalentTo(new int[0]));
    }

    [Test]
    public void Compute2_2()
    {
        var result = Subsets.Compute(2, 2).ToArray();
        Assert.That(result.Length, Is.EqualTo(1));
        Assert.That(result[0], Is.EquivalentTo(new[] { 0, 1 }));
    }

    [Test]
    public void Compute2_1()
    {
        var result = Subsets.Compute(2, 1).ToArray();
        Assert.That(result.Length, Is.EqualTo(2));
        Assert.That(result[0], Is.EquivalentTo(new[] { 0 }));
        Assert.That(result[1], Is.EquivalentTo(new[] { 1 }));
    }

    [Test]
    public void Compute2_0()
    {
        var result = Subsets.Compute(2, 0).ToArray();
        Assert.That(result.Length, Is.EqualTo(1));
        Assert.That(result[0], Is.EquivalentTo(new int[0]));
    }

    [Test]
    public void Compute3_3()
    {
        var result = Subsets.Compute(3, 3).ToArray();
        Assert.That(result.Length, Is.EqualTo(1));
        Assert.That(result[0], Is.EquivalentTo(new[] { 0, 1, 2 }));
    }

    [Test]
    public void Compute3_2()
    {
        var result = Subsets.Compute(3, 2).ToArray();
        Assert.That(result.Length, Is.EqualTo(3));
        Assert.That(result[0], Is.EquivalentTo(new[] { 0, 1 }));
        Assert.That(result[1], Is.EquivalentTo(new[] { 0, 2 }));
        Assert.That(result[2], Is.EquivalentTo(new[] { 1, 2 }));
    }

    [Test]
    public void Compute3_1()
    {
        var result = Subsets.Compute(3, 1).ToArray();
        Assert.That(result.Length, Is.EqualTo(3));
        Assert.That(result[0], Is.EquivalentTo(new[] { 0 }));
        Assert.That(result[1], Is.EquivalentTo(new[] { 1 }));
        Assert.That(result[2], Is.EquivalentTo(new[] { 2 }));
    }

    [Test]
    public void Compute3_0()
    {
        var result = Subsets.Compute(2, 0).ToArray();
        Assert.That(result.Length, Is.EqualTo(1));
        Assert.That(result[0], Is.EquivalentTo(new int[0]));
    }

    [Test]
    public void Compute4_4()
    {
        var result = Subsets.Compute(4, 4).ToArray();
        Assert.That(result.Length, Is.EqualTo(1));
        Assert.That(result[0], Is.EquivalentTo(new[] { 0, 1, 2, 3 }));
    }

    [Test]
    public void Compute4_3()
    {
        var result = Subsets.Compute(4, 3).ToArray();
        Assert.That(result.Length, Is.EqualTo(4));
        Assert.That(result[0], Is.EquivalentTo(new[] { 0, 1, 2 }));
        Assert.That(result[1], Is.EquivalentTo(new[] { 0, 1, 3 }));
        Assert.That(result[2], Is.EquivalentTo(new[] { 0, 2, 3 }));
        Assert.That(result[3], Is.EquivalentTo(new[] { 1, 2, 3 }));
    }

    [Test]
    public void Compute4_2()
    {
        var result = Subsets.Compute(4, 2).ToArray();
        Assert.That(result.Length, Is.EqualTo(6));
        Assert.That(result[0], Is.EquivalentTo(new[] { 0, 1 }));
        Assert.That(result[1], Is.EquivalentTo(new[] { 0, 2 }));
        Assert.That(result[2], Is.EquivalentTo(new[] { 0, 3 }));
        Assert.That(result[3], Is.EquivalentTo(new[] { 1, 2 }));
        Assert.That(result[4], Is.EquivalentTo(new[] { 1, 3 }));
        Assert.That(result[5], Is.EquivalentTo(new[] { 2, 3 }));
    }

    [Test]
    public void Compute4_1()
    {
        var result = Subsets.Compute(4, 1).ToArray();
        Assert.That(result.Length, Is.EqualTo(4));
        Assert.That(result[0], Is.EquivalentTo(new[] { 0 }));
        Assert.That(result[1], Is.EquivalentTo(new[] { 1 }));
        Assert.That(result[2], Is.EquivalentTo(new[] { 2 }));
        Assert.That(result[3], Is.EquivalentTo(new[] { 3 }));
    }

    [Test]
    public void Compute5_5()
    {
        var result = Subsets.Compute(5, 5).ToArray();
        Assert.That(result.Length, Is.EqualTo(1));
        Assert.That(result[0], Is.EquivalentTo(new[] { 0, 1, 2, 3, 4 }));
    }

    [Test]
    public void Compute5_4()
    {
        var result = Subsets.Compute(5, 4).ToArray();
        Assert.That(result.Length, Is.EqualTo(5));
        Assert.That(result[0], Is.EquivalentTo(new[] { 0, 1, 2, 3 }));
        Assert.That(result[1], Is.EquivalentTo(new[] { 0, 1, 2, 4 }));
        Assert.That(result[2], Is.EquivalentTo(new[] { 0, 1, 3, 4 }));
        Assert.That(result[3], Is.EquivalentTo(new[] { 0, 2, 3, 4 }));
        Assert.That(result[4], Is.EquivalentTo(new[] { 1, 2, 3, 4 }));
    }

    [Test]
    public void Compute5_3()
    {
        var result = Subsets.Compute(5, 3).ToArray();
        Assert.That(result.Length, Is.EqualTo(10));
        var i = 0;
        Assert.That(result[i++], Is.EquivalentTo(new[] { 0, 1, 2 }));
        Assert.That(result[i++], Is.EquivalentTo(new[] { 0, 1, 3 }));
        Assert.That(result[i++], Is.EquivalentTo(new[] { 0, 1, 4 }));
        Assert.That(result[i++], Is.EquivalentTo(new[] { 0, 2, 3 }));
        Assert.That(result[i++], Is.EquivalentTo(new[] { 0, 2, 4 }));
        Assert.That(result[i++], Is.EquivalentTo(new[] { 0, 3, 4 }));
        Assert.That(result[i++], Is.EquivalentTo(new[] { 1, 2, 3 }));
        Assert.That(result[i++], Is.EquivalentTo(new[] { 1, 2, 4 }));
        Assert.That(result[i++], Is.EquivalentTo(new[] { 1, 3, 4 }));
        Assert.That(result[i++], Is.EquivalentTo(new[] { 2, 3, 4 }));
    }

    [Test]
    public void Compute5_2()
    {
        var result = Subsets.Compute(5, 2).ToArray();
        Assert.That(result.Length, Is.EqualTo(10));
        var i = 0;
        Assert.That(result[i++], Is.EquivalentTo(new[] { 0, 1 }));
        Assert.That(result[i++], Is.EquivalentTo(new[] { 0, 2 }));
        Assert.That(result[i++], Is.EquivalentTo(new[] { 0, 3 }));
        Assert.That(result[i++], Is.EquivalentTo(new[] { 0, 4 }));
        Assert.That(result[i++], Is.EquivalentTo(new[] { 1, 2 }));
        Assert.That(result[i++], Is.EquivalentTo(new[] { 1, 3 }));
        Assert.That(result[i++], Is.EquivalentTo(new[] { 1, 4 }));
        Assert.That(result[i++], Is.EquivalentTo(new[] { 2, 3 }));
        Assert.That(result[i++], Is.EquivalentTo(new[] { 2, 4 }));
        Assert.That(result[i++], Is.EquivalentTo(new[] { 3, 4 }));
    }

    [Test]
    public void Compute5_1()
    {
        var result = Subsets.Compute(5, 1).ToArray();
        Assert.That(result.Length, Is.EqualTo(5));
        var i = 0;
        Assert.That(result[i++], Is.EquivalentTo(new[] { 0 }));
        Assert.That(result[i++], Is.EquivalentTo(new[] { 1 }));
        Assert.That(result[i++], Is.EquivalentTo(new[] { 2 }));
        Assert.That(result[i++], Is.EquivalentTo(new[] { 3 }));
        Assert.That(result[i++], Is.EquivalentTo(new[] { 4 }));
    }
}

0

0
我创建了以下IEnumerable扩展方法,以获取数组中大小为n的所有子集,并带有可选的谓词。
public static class ExtensionMethods
{
    public static IEnumerable<IEnumerable<T>> GetSubSets<T>(this IEnumerable<T> list, int size, Predicate<T> filter)
    {
        return GetSubSetsInternal(list.ToList().FindAll(filter), size);
    }
    public static IEnumerable<IEnumerable<T>> GetSubSets<T>(this IEnumerable<T> list, int size)
    {
        return GetSubSetsInternal(list, size);
    }
    private static IEnumerable<IEnumerable<T>> GetSubSetsInternal<T>(this IEnumerable<T> list, int size)
    {
        var vFinalList = new List<IList<T>>();
        var c = size;

        var vList = list.ToList();

        var vListSize = list.Count();

        if (c <= 0 || size > vListSize) return vFinalList;

        c -= 1;

        var indexs = new int[size];
        var vFirstSubList = new List<T>();

        for (var j = 0; j <= (size - 1); j++)
        {
            indexs[j] = j;
            vFirstSubList.Add(vList[j]);
        }


        vFinalList.Add(vFirstSubList);

        while (indexs[0] !=(vListSize - size) && vFinalList.Count < 2147483647)
        {
            if (indexs[c] < c + (vListSize - size))
            {
                indexs[c]++;

                var vSubList = new List<T>();
                foreach (var j in indexs) vSubList.Add(vList[j]);
                vFinalList.Add(vSubList);
            }
            else
            {
                do
                {
                    c -= 1;
                } while (indexs[c] == c + (vListSize - size));
                indexs[c] += 1;
                for (var j = c + 1; j <= (size - 1); j++) indexs[j] = indexs[j - 1] + 1;
                var vSubList = new List<T>();
                foreach (var j in indexs) vSubList.Add(vList[j]);
                vFinalList.Add(vSubList);
                c = size - 1;
            }
        }
        return vFinalList;
    }
}

用法:例如在数独中确定裸单元组。

internal class SolverStepNakedSet : SolverStepSetBase
{

    protected override bool SolveIt(SolutionManager solutionManager, int size)
    {
        foreach (var vSol in (from vec in solutionManager.Sudoku.Vectors
                              from vSet in vec.GetSubSets(size, c => Enumerable.Range(2, size - 1).Contains(c.MyValue.OpenCandidates.Count()))
                              let vValues = vSet.SelectMany(c => c.MyValue.OpenCandidates).Select(cc=> cc.Value).Distinct().ToList()
                              where vValues.Count() == size
                              let vLstCellsValues = (from vValue in vValues
                                                     let vLstCells = vec.FindAll(c => !vSet.Contains(c) && c.MyValue.OpenCandidates.Any(cc=>cc.Value==vValue))
                                                     where vLstCells.Count() > 0
                                                     select new SolutionValueElement(vValue, vLstCells))
                              where vLstCellsValues.Count() > 0
                              select new SolutionSet( Step, new SolutionValue(ValueState.Removal, vLstCellsValues), new Set(vSet.ToList(),vValues))))
        {

            if (solutionManager.Execute(vSol)) return true;
        }
        return false;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接