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