如何有效地生成总和在指定范围内的所有组合(在所有深度)

时间:2013-10-10 01:09:14

标签: c# algorithm recursion combinatorics

假设您有一组值(111121216),您会怎样?生成所有可能的组合(不重复),其总和在预定义范围[min,max]内。例如,以下是1317之间范围的所有组合(所有深度):

1 12

1 1 12

1 1 1 12

16

1 16

这假设具有相同值的每个项目都是无法区分的,因此在最终输出中没有1 12的三个结果。蛮力是可能的,但是在物品数量很大的情况下,所有深度的组合数量都是天文数字。在上面的示例中,在所有深度处存在(3 + 1)*(2 + 1)*(1 + 1)= 24个组合。因此,总组合是任何给定值+ 1的项数的乘积。当然,我们可以逻辑地丢弃其部分和大于最大值的大量组合(例如集合16 { {1}}已经大于12的最大值,因此请跳过其中包含1716的任何组合。

我原本以为我可以将输入数组转换为两个数组并将它们增加为类似里程表。但是我完全陷入了这种早期打破的递归算法。有什么建议吗?

12

修改

整数值仅用于演示。它可以是具有某种数值的任何对象({ int uniqueValues = 3; int[] maxCounts = new int[uniqueValues]; int[] values = new int[uniqueValues]; // easy code to bin the data, just hardcoding for example maxCounts[0] = 3; values[0] = 1; maxCounts[1] = 2; values[1] = 12; maxCounts[2] = 1; values[2] = 16; GenerateCombinationsHelper(new List<int[]>(), 13, 17, 0, 0, maxCounts, new int[3], values); } private void GenerateCombinationsHelper(List<int[]> results, int min, int max, int currentValue, int index, int[] maxValues, int[] currentCombo, int[] values) { if (index >= maxValues.Length) { return; } while (currentCombo[index] < maxValues[index]) { currentValue += values[index]; if (currentValue> max) { return; } currentCombo[index]++; if (currentValue< min) { GenerateCombinationsHelper(results, min, max, currentValue, index + 1, maxValues, currentCombo, values); } else { results.Add((int[])currentCombo.Clone()); } } } intdouble等......)

通常只会有少数独特的值(约10个左右),但总共可以有数千个。

5 个答案:

答案 0 :(得分:1)

将主要呼叫切换到:

GenerateCombinationsHelper2(new List<int[]>(), 13, 17, 0, maxCounts, new int[3], values);

然后添加此代码:

private void GenerateCombinationsHelper2(List<int[]> results, int min, int max, int index, int[] maxValues, int[] currentCombo, int[] values)
{
    int max_count = Math.Min((int)Math.Ceiling((double)max / values[index]), maxValues[index]);

    for(int count = 0; count <= max_count; count++)
    {
        currentCombo[index] = count;
        if(index < currentCombo.Length - 1)
        {
            GenerateCombinationsHelper2(results, min, max, index + 1, maxValues, currentCombo, values);
        }
        else
        {
            int sum = Sum(currentCombo, values);
            if(sum >= min && sum <= max)
            {
                int[] copy = new int[currentCombo.Length];
                Array.Copy(currentCombo, copy, copy.Length);
                results.Add(copy);
            }
        }
    }
}

private static int Sum(int[] combo, int[] values)
{
    int sum = 0;
    for(int i = 0; i < combo.Length; i++)
    {
        sum += combo[i] * values[i];
    }
    return sum;
}

它返回5个有效答案。

答案 1 :(得分:1)

这种问题的一般趋势是,会出现相对较少的值,但每个值都会显示很多次。因此,您首先要创建一个数据结构,该数据结构可以有效地描述将添加到所需值的组合,然后才能找出所有这样做的组合。 (如果您知道术语“动态编程”,那正是我所描述的方法。)

C#术语中明显的数据结构是Hashtable,其键是组合加起来的总数,其值是列出最后元素位置的数组,这些元素可以组合使用,可以加起来那个特殊的总数。

如何构建该数据结构?

首先从一个Hashtable开始,它包含总数0作为键,空数组作为值。然后,对于数组的每个元素,您可以创建一个可以从之前的总计中获得的新总计列表,并将元素的位置附加到它们的每个值(如果需要,可以插入一个新的值)。当您浏览完所有元素后,您就拥有了数据结构。

现在,您可以仅针对所需范围内的总计搜索该数据结构。对于每个这样的总数,您可以编写一个递归程序,它将遍历您的数据结构以生成组合。这个步骤确实可以产生组合爆炸,但好的是,每个组合产生的实际上是你最终答案的组合。因此,如果这个阶段需要很长时间,那是因为你有很多最终答案!

答案 2 :(得分:0)

试试这个算法

int arr[] = {1,1,1,12,12,16}
for(int i = 0;i<2^arr.Length;i++)
{
int[] arrBin = BinaryFormat(i); // binary format i
for(int j = 0;j<arrBin.Length;j++)
  if (arrBin[j] == 1)
     Console.Write("{0} ", arr[j]);
Console.WriteLine();
}

答案 3 :(得分:0)

这与恰好是subset sum problemNP-complete非常相似。

维基百科对以下关于NP完全问题的说法如下:

  

尽管可以快速验证此问题的任何解决方案,   没有已知的有效方法来在第一个中找到解决方案   地点;实际上,NP完全问题最显着的特征   是因为没有快速解决它们的方法。也就是说,所需的时间   使用任何当前已知的算法来解决问题的增加   问题的规模越来越大。这意味着   解决许多这些中等尺寸版本所需的时间   问题很容易达到数十亿或数万亿年,   使用今天可用的任何计算能力。作为结果,   确定是否可以解决这些问题   很快,称为P与NP问题,是主要原因之一   今天计算机科学中尚未解决的问题。

如果确实有一种方法可以解决这个问题,除了通过powerset强制执行并找到总和达到给定范围内的值的所有子集,那么我会非常有兴趣听到它。

答案 4 :(得分:0)

另一个实现的想法:

从数字列表中创建一个堆栈列表,每个堆栈代表一个出现在列表中的数字,并且这个数字被推入堆栈中的次数与他在数字列表中出现的次数相同。更重要的是,此列表已排序。

这个想法是你遍历堆栈列表,在每个堆栈中,如果它没有超过最大值,你一次弹出一个数字并调用该函数,并执行另一个跳过当前堆栈的调用。 / p>

此算法减少了许多冗余计算,例如在添加此值超过最大值时尝试添加具有相同值的不同元素。

我能够用这个算法解决相当大的问题(50个数字以上),具体取决于最小值和最大值,显然当间隔非常大时,组合的数量可能很大。

以下是代码:

static void GenerateLimitedCombinations(List<int> intList, int minValue, int maxValue)
{
    intList.Sort();
    List<Stack<int>> StackList = new List<Stack<int>>();
    Stack<int> NewStack = new Stack<int>();
    NewStack.Push(intList[0]);
    StackList.Add(NewStack);

    for (int i = 1; i < intList.count; i++)
    {
        if (intList[i - 1] == intList[i])
            StackList[StackList.count - 1].Push(intList[i]);
        else
        {
            NewStack = new Stack<int>();
            NewStack.Push(intList[i]);
            StackList.Add(NewStack);
        }
    }

    GenerateLimitedCombinations(StackList, minValue, maxValue, 0, new List<int>(), 0);
}

static void GenerateLimitedCombinations(List<Stack<int>> stackList, int minValue, int maxValue, int currentStack, List<int> currentCombination, int currentSum)
{
    if (currentStack == stackList.count)
    {
        if (currentSum >= minValue)
        {
            foreach (int tempInt in CurrentCombination)
            {
                Console.Write(tempInt + " ");
            }
            Console.WriteLine(;
        }
    }

    else
    {
        int TempSum = currentSum;
        List<int> NewCombination = new List<int>(currentCombination);
        Stack<int> UndoStack = new Stack<int>();

        while (stackList[currentStack].Count != 0 && stackList[currentStack].Peek() + TempSum <= maxValue)
        {
            int AddedValue = stackList[currentStack].Pop();
            UndoStack.Push(AddedValue);
            NewCombination.Add(AddedValue);
            TempSum += AddedValue;
            GenerateLimitedCombinations(stackList, minValue, maxValue, currentStack + 1, new List<int>(NewCombination), TempSum);
        }

        while (UndoStack.Count != 0)
        {
            stackList[currentStack].Push(UndoStack.Pop());
        }

        GenerateLimitedCombinations(stackList, minValue, maxValue, currentStack + 1, currentCombination, currentSum);
    }
}

这是一个测试程序:

static void Main(string[] args)
{
    Random Rnd = new Random();
    List<int> IntList = new List<int>();
    int NumberOfInts = 10, MinValue = 19, MaxValue 21;

    for (int i = 0; i < NumberOfInts; i++) { IntList.Add(Rnd.Next(1, 10));
    for (int i = 0; i < NumberOfInts; i++) { Console.Write(IntList[i] + " "); } Console.WriteLine(); Console.WriteLine();

    GenerateLimitedCombinations(IntList, MinValue, MaxValue);
    Console.ReadKey();
}