C# 查找与给定数字相加的数字子集

C# 查找与给定数字相加的数字子集,c#,algorithm,sum,subset,C#,Algorithm,Sum,Subset,我有一个问题需要用C#来解决。有一个十进制数数组(表示仓库在不同时间收到的物品数量)。此数组已按接收数量的顺序排序。我需要能够找到最早的数量组合,这些数量总和为指定的总量 例如,假设我有一些数量,按时间顺序排列如下[13,6,9,8,23,18,4],我要匹配的总量是23。那么我应该能够得到[13,6,4]作为匹配子集,尽管[6,9,8]和[23]也是匹配的,但不是最早的 这方面的最佳方法/算法是什么 到目前为止,我已经提出了一种使用递归的相当幼稚的方法 public class MatchSu

我有一个问题需要用C#来解决。有一个十进制数数组(表示仓库在不同时间收到的物品数量)。此数组已按接收数量的顺序排序。我需要能够找到最早的数量组合,这些数量总和为指定的总量

例如,假设我有一些数量,按时间顺序排列如下[13,6,9,8,23,18,4],我要匹配的总量是23。那么我应该能够得到[13,6,4]作为匹配子集,尽管[6,9,8]和[23]也是匹配的,但不是最早的

这方面的最佳方法/算法是什么

到目前为止,我已经提出了一种使用递归的相当幼稚的方法

public class MatchSubset
{
    private decimal[] qty = null;
    private decimal matchSum = 0;
    public int operations = 0;
    public int[] matchedIndices = null;
    public int matchCount = 0;
    private bool SumUp(int i, int n, decimal sum)
    {
        operations++;
        matchedIndices[matchCount++] = i;
        sum += qty[i];
        if (sum == matchSum)
            return true;
        if (i >= n - 1)
        {
            matchCount--;
            return false;
        }
        if (SumUp(i + 1, n, sum))
            return true;

        sum -= qty[i];
        matchCount--;
        return SumUp(i + 1, n, sum);
    }
    public bool Match(decimal[] qty, decimal matchSum)
    {
        this.qty = qty;
        this.matchSum = matchSum;
        matchCount = 0;
        matchedIndices = new int[qty.Count()];
        return SumUp(0, qty.Count(), 0);
    }
}

static void Main(string[] args)
{
    var match = new MatchSubset();
    int maxQtys = 20;
    Random rand = new Random(DateTime.Now.Millisecond);
    decimal[] qty = new decimal[maxQtys];
    for (int i = 0; i < maxQtys - 2; i++)
        qty[i] = rand.Next(1, 500);

    qty[maxQtys - 2] = 99910;
    qty[maxQtys - 1] = 77910;
    DateTime t1 = DateTime.Now;
    if (match.Match(qty, 177820))
    {
        Console.WriteLine(DateTime.Now.Subtract(t1).TotalMilliseconds);
        Console.WriteLine("Operations: " + match.operations);
        for (int i = 0; i < match.matchCount; i++)
        {
            Console.WriteLine(match.matchedIndices[i]);
        }
    }
}
公共类匹配子集
{
私有十进制[]数量=空;
私有十进制匹配和=0;
公共整数运算=0;
public int[]matchedinces=null;
公共整数匹配计数=0;
私有布尔汇总(整数i、整数n、十进制和)
{
操作++;
MatchedDices[MatcheCount++]=i;
总和+=数量[i];
if(sum==matchSum)
返回true;
如果(i>=n-1)
{
匹配计数--;
返回false;
}
if(总和(i+1,n,总和))
返回true;
总和-=数量[i];
匹配计数--;
收益汇总(i+1,n,总和);
}
公共布尔匹配(十进制[]数量,十进制匹配和)
{
该数量=数量;
this.matchSum=matchSum;
匹配计数=0;
MatchedDices=新整数[qty.Count()];
返回汇总(0,数量计数(),0);
}
}
静态void Main(字符串[]参数)
{
var match=新的MatchSubset();
int maxQtys=20;
Random rand=新随机数(DateTime.Now.毫秒);
小数点[]数量=新小数点[maxQtys];
对于(int i=0;i
匹配子集可以短到一个元素,也可以长到原始集合(包含所有元素)。但是为了测试最坏的情况,在我的测试程序中,我使用了一个任意长的集合,其中只有最后两个匹配给定的数字

我看到集合中有20个数字,它调用递归函数超过一百万次,最大递归深度为20。如果我在生产中遇到一组30个或更多的数字,我担心这将耗费很长时间


有没有办法进一步优化这一点?另外,看看下面的投票结果,这是不是提出此类问题的错误地点?

我无法以革命性的方式结束,因此提出的解决方案只是相同暴力算法的不同实现,有两个优化。第一个优化是使用迭代实现,而不是递归实现。我不认为这有什么意义,因为您更有可能最终导致时间不足,而不是堆栈空间不足,但总体而言,这是一个很好的解决方案,并且不难实现。最重要的是第二个。其思想是,在“前进”步骤中,只要当前和大于目标和,就可以跳过检查下一个与当前项具有更大或相等值的项。通常,这是通过首先对输入集进行排序来完成的,这在您的案例中不适用。然而,在思考如何克服这一限制时,我意识到我所需要的是每个项目都有下一个项目的索引,它的值小于项目的值,因此我可以直接跳到该索引,直到到达终点

现在,尽管在最坏的情况下,两种实现的执行方式相同,即可能无法在合理的时间内结束,但在许多实际场景中,优化的变体能够非常快地产生结果,而原始的变体仍然无法在合理的时间内结束。您可以通过使用
maxQtys
maxQty
参数来检查差异

以下是描述的实现,以及测试代码:

using System;
using System.Diagnostics;
using System.Linq;

namespace Tests
{
    class Program
    {
        private static void Match(decimal[] inputQty, decimal matchSum, out int[] matchedIndices, out int matchCount, out int operations)
        {
            matchedIndices = new int[inputQty.Length];
            matchCount = 0;
            operations = 0;

            var nextLessQtyPos = new int[inputQty.Length];
            for (int i = inputQty.Length - 1; i >= 0; i--)
            {
                var currentQty = inputQty[i];
                int nextPos = i + 1;
                while (nextPos < inputQty.Length)
                {
                    var nextQty = inputQty[nextPos];
                    int compare = nextQty.CompareTo(currentQty);
                    if (compare < 0) break;
                    nextPos = nextLessQtyPos[nextPos];
                    if (compare == 0) break;
                }
                nextLessQtyPos[i] = nextPos;
            }

            decimal currentSum = 0;
            for (int nextPos = 0; ;)
            {
                if (nextPos < inputQty.Length)
                {
                    // Forward
                    operations++;
                    var nextSum = currentSum + inputQty[nextPos];
                    int compare = nextSum.CompareTo(matchSum);
                    if (compare < 0)
                    {
                        matchedIndices[matchCount++] = nextPos;
                        currentSum = nextSum;
                        nextPos++;
                    }
                    else if (compare > 0)
                    {
                        nextPos = nextLessQtyPos[nextPos];
                    }
                    else
                    {
                        // Found
                        matchedIndices[matchCount++] = nextPos;
                        break;
                    }
                }
                else
                {
                    // Backward
                    if (matchCount == 0) break;
                    var lastPos = matchedIndices[--matchCount];
                    currentSum -= inputQty[lastPos];
                    nextPos = lastPos + 1;
                }
            }
        }

        public class MatchSubset
        {
            private decimal[] qty = null;
            private decimal matchSum = 0;
            public int operations = 0;
            public int[] matchedIndices = null;
            public int matchCount = 0;
            private bool SumUp(int i, int n, decimal sum)
            {
                operations++;
                matchedIndices[matchCount++] = i;
                sum += qty[i];
                if (sum == matchSum)
                    return true;
                if (i >= n - 1)
                {
                    matchCount--;
                    return false;
                }
                if (SumUp(i + 1, n, sum))
                    return true;

                sum -= qty[i];
                matchCount--;
                return SumUp(i + 1, n, sum);
            }
            public bool Match(decimal[] qty, decimal matchSum)
            {
                this.qty = qty;
                this.matchSum = matchSum;
                matchCount = 0;
                matchedIndices = new int[qty.Count()];
                return SumUp(0, qty.Count(), 0);
            }
        }

        static void Main(string[] args)
        {
            int maxQtys = 3000;
            decimal matchQty = 177820;
            var qty = new decimal[maxQtys];
            int maxQty = (int)(0.5m * matchQty);
            var random = new Random();
            for (int i = 0; i < maxQtys - 2; i++)
                qty[i] = random.Next(1, maxQty);

            qty[maxQtys - 2] = 99910;
            qty[maxQtys - 1] = 77910;

            Console.WriteLine("Source: {" + string.Join(", ", qty.Select(v => v.ToString())) + "}");
            Console.WriteLine("Target: {" + matchQty + "}");

            int[] matchedIndices;
            int matchCount;
            int operations;
            var sw = new Stopwatch();

            Console.Write("#1 processing...");
            sw.Restart();
            Match(qty, matchQty, out matchedIndices, out matchCount, out operations);
            sw.Stop();
            ShowResult(matchedIndices, matchCount, operations, sw.Elapsed);

            Console.Write("#2 processing...");
            var match = new MatchSubset();
            sw.Restart();
            match.Match(qty, matchQty);
            sw.Stop();
            ShowResult(match.matchedIndices, match.matchCount, match.operations, sw.Elapsed);

            Console.Write("Done.");
            Console.ReadLine();
        }

        static void ShowResult(int[] matchedIndices, int matchCount, int operations, TimeSpan time)
        {
            Console.WriteLine();
            Console.WriteLine("Time: " + time);
            Console.WriteLine("Operations: " + operations);
            if (matchCount == 0)
                Console.WriteLine("No Match.");
            else
                Console.WriteLine("Match: {" + string.Join(", ", Enumerable.Range(0, matchCount).Select(i => matchedIndices[i].ToString())) + "}");
        }
    }
}
使用系统;
使用系统诊断;
使用System.Linq;
命名空间测试
{
班级计划
{
私有静态无效匹配(十进制[]输入数量、十进制匹配和、out int[]匹配数、out int匹配计数、out int操作)
{
MatchedDices=新整数[输入数量长度];
匹配计数=0;
操作=0;
var nextLessQtyPos=新整数[输入数量长度];
对于(int i=输入数量长度-1;i>=0;i--)
{
var currentQty=输入数量[i];
int nextPos=i+1;
while(nextPos