什么是一种非递归算法,用于确定是否可以从一组数字中加法构建传入的数量。
就我而言,我正在确定是否可以通过将一组钞票(例如 5 美元、10 美元和 20 美元钞票)的某种组合相加来满足某个货币金额(例如 40 美元)。这是一个简单的例子,但算法需要适用于任何货币集(一些货币使用时髦的账单金额,而一些账单可能在给定时间不可用)。
所以 50 美元可以用一组(20 美元和 30 美元)来满足,但不能用一组(20 美元和 40 美元)来满足。非递归要求是由于目标代码库用于SQL Server 2000
递归支持有限的地方。
此外,这是为了支持多币种环境,其中可用的账单集可能会发生变化(例如外币兑换柜员)。
9 回答
您曾两次表示该算法不能递归,但这是该问题的自然解决方案。一种或另一种方式,您将需要执行搜索来解决此问题。如果递归结束,您将需要手动回溯。
选择低于目标值的最大货币值。如果匹配,你就完成了。如果不是,则将当前目标值压入堆栈并从目标值中减去选取的货币值。继续这样做,直到您找到匹配项或没有更多的货币价值。然后使用堆栈回溯并选择不同的值。
基本上,它是带有手动管理堆栈的循环内的递归解决方案。
如果您将每个面额视为以 n 为底的数字上的一个点,其中 n 是您需要的最大纸币数量,那么您可以递增该数字,直到您用尽问题空间或找到解决方案。
您需要的最大纸币数量是您需要的总数除以最低面额的纸币。
这是对问题的蛮力回应,但它肯定会奏效。
这是一些p代码。我的栅栏柱子可能到处都是,而且它的优化太荒谬了,但它应该可以工作。反正我觉得这个想法是对的。
Denominations = [10,20,50,100]
Required = 570
Denominations = sort(Denominations)
iBase = integer (Required / Denominations[1])
BumpList = array [Denominations.count]
BumpList.Clear
repeat
iTotal = 0
for iAdd = 1 to Bumplist.size
iTotal = iTotal + bumplist [iAdd] * Denominations[iAdd]
loop
if iTotal = Required then exit true
//this bit should be like a mileometer.
//We add 1 to each wheel, and trip over to the next wheel when it gets to iBase
finished = true
for iPos from bumplist.last to bumplist.first
if bumplist[iPos] = (iBase-1) then bumplist[iPos] = 0
else begin
finished = false
bumplist[iPos] = bumplist[iPos]+1
exit for
end
loop
until (finished)
exit false
这是一个可以通过称为动态规划的方法来解决的问题。不幸的是,我的讲义太专注于生物信息学,所以你必须自己去谷歌搜索。
这听起来像子集和问题,已知是 NP-complete。
祝你好运。
编辑:如果您允许任意数量的某种面额的钞票/硬币(而不是一个),那么这是一个不同的问题,并且更容易。看硬币问题。在阅读(可疑)类似问题的另一个答案时,我意识到了这一点。
您可以使用像 MattW这样的动态编程方法来处理这个问题。提及。
鉴于账单数量和最大金额有限,您可以尝试以下解决方案。代码片段在 C# 中,但我相信您可以轻松地将其移植到其他语言。
// Set of bills
int[] unit = { 40,20,70};
// Max amount of money
int max = 100000;
bool[] bucket = new bool[max];
foreach (int t in unit)
bucket[t] = true;
for (int i = 0; i < bucket.Length; i++)
if (bucket[i])
foreach (int t in unit)
if(i + t < bucket.Length)
bucket[i + t] = true;
// Check if the following amount of money
// can be built additively
Console.WriteLine("15 : " + bucket[15]);
Console.WriteLine("50 : " + bucket[50]);
Console.WriteLine("60 : " + bucket[60]);
Console.WriteLine("110 : " + bucket[110]);
Console.WriteLine("120 : " + bucket[120]);
Console.WriteLine("150 : " + bucket[150]);
Console.WriteLine("151 : " + bucket[151]);
输出:
15 : False
50 : False
60 : True
110 : True
120 : True
150 : True
151 : False
我同意Tyler的观点- 你所描述的是Subset Sum问题的一个变体,它被称为NP-Complete。在这种情况下,您有点幸运,因为您使用的是一组有限的值,因此您可以在此处使用动态编程技术来稍微优化问题。就代码的一些一般想法而言:
- 由于您正在处理金钱,因此只有这么多方法可以使用给定的账单进行更改,并且在大多数情况下,某些账单的使用频率高于其他账单。因此,如果您存储结果,您可以保留一组最常见的解决方案,然后在尝试找到实际解决方案之前检查它们。
- 除非您使用的语言不支持递归,否则没有理由完全忽略解决方案中递归的使用。虽然任何递归问题都可以使用迭代来解决,但在这种情况下,递归可能更容易编写。
其他一些用户,例如Kyle和seanyboy,为您指明了编写自己的函数的正确方向,因此您应该看看他们为您的工作提供了什么。
无递归和有限递归是有区别的。不要混淆这两者,因为你会错过课程的重点。
例如,您可以安全地使用 C++ 或其他低级语言中的递归来编写阶乘函数,因为您的结果将溢出即使是您的最大数量的容器,但只有几个递归。因此,您将面临的问题将是在结果由于递归而炸毁您的堆栈之前存储结果。
这就是说,无论你找到什么解决方案——我什至没有费心去深入理解你的问题,因为我看到其他人已经这样做了——你必须研究你的算法的行为,你可以确定最坏情况的深度你的堆栈。
如果您的平台支持最坏的情况,您不需要完全避免递归。
算法: 1. 按降序排列可用的货币面额。
2. 计算余数 = Input % denomination[i] i -> n-1, 0
3. 如果余数为0,则输入可以分解,否则不能分解。
示例:
输入:50,可用:10,20
[50 % 20] = 10,[10 % 10] = 0,回答:是 输入:50,可用:15,20
[50 % 20] = 10,[10 % 15] = 15,答案:否
编辑:以下内容有时会起作用。想想为什么它不能一直工作,以及如何改变它以涵盖其他情况。
从最大的账单开始构建它。这将产生最少数量的票据。
在不超过价格的情况下,尽可能多地使用初始金额并应用最大的账单。
转到下一个最大的账单并以相同的方式应用它。
继续这样做,直到你拿到最小的账单。
然后检查总和是否等于目标金额。