haskell代码中的堆栈溢出
我想找到所有硬币兑换组合。1,2,5,10,20,50,100和200。(1分,2分) 如果coin超过500(5欧元),它应该给出-1。我的代码与这些测试用例完美配合:numOfSplits 10(11)numOfSplits 20(41)numOfSplits 100(4563)。当我尝试使用测试用例numOfSplits 200或500时,它将给出C堆栈溢出错误。如何改进我的代码haskell代码中的堆栈溢出,haskell,recursion,stack-overflow,coin-change,Haskell,Recursion,Stack Overflow,Coin Change,我想找到所有硬币兑换组合。1,2,5,10,20,50,100和200。(1分,2分) 如果coin超过500(5欧元),它应该给出-1。我的代码与这些测试用例完美配合:numOfSplits 10(11)numOfSplits 20(41)numOfSplits 100(4563)。当我尝试使用测试用例numOfSplits 200或500时,它将给出C堆栈溢出错误。如何改进我的代码 numOfSplits :: Integer -> Integer numOfSplits a
numOfSplits :: Integer -> Integer
numOfSplits a
| (abs a) > 500 = -1
| (abs a) == 0 = 0
| otherwise = intzahler (makeChange [200,100,50,20,10,5,2,1] (abs a) 200)
intzahler :: [[Integer]] -> Integer
intzahler array
| array == [] = 0
| otherwise = 1 + intzahler (tail array)
makeChange :: [Integer] -> Integer -> Integer -> [[Integer]]
makeChange coins amount maxCoins
| amount < 0 = []
| amount == 0 = [[]]
| null coins = []
| amount `div` maximum coins > maxCoins = [] -- optimisation
| amount > 0 =
do x <- coins
xs <- makeChange (filter (<= x) coins)
(amount - x)
(maxCoins - 1)
guard (genericLength (x:xs) <= maxCoins)
return (x:xs)
numOfSplits::Integer->Integer
努莫夫分裂
|(abs a)>500=-1
|(abs a)==0=0
|否则=intzahler(makeChange[200100,50,20,10,5,2,1](abs a)200)
intzahler::[[Integer]]->Integer
因扎勒阵列
|数组==[]=0
|否则=1+intzahler(尾部阵列)
makeChange::[Integer]->Integer->Integer->[[Integer]]
makeChange硬币数量maxCoins
|金额<0=[]
|金额==0=[]]
|零硬币=[]
|金额'div'最大硬币数>最大硬币数=[]--优化
|金额>0=
dox[[Integer]]
零钱金额
|金额<0=[]
|金额==0=[]]
|零硬币=[]
|金额>0=
您是在编译程序还是在ghci中运行程序?你在哪个站台
numOfSplits 200
编译时应该只需要大约6秒
以下是您在ideaone.com上的代码:
对于180的输入,它的运行时间不到5秒(这是该站点允许的最大运行时间)
如前所述,您的intzahler
函数与genericLength
或甚至与length
相同,尽管在这种情况下,它似乎没有多大区别。快速解决此问题,从而避免耗尽计算机的资源,如堆栈,要求重新使用以前计算的部分答案
让我们假设我们想解决一个类似的问题,我们试图找出有多少种方法可以只使用1美分、2美分或5美分的硬币就赚15美分。我们将解决两个问题-第一个是正确地解决问题。第二个是快速解决问题
正确地解决问题
为了正确解决这个问题,我们需要避免重新计算已经计算过的硬币组合。例如,我们可以通过以下方式赚取15美分:
- 2枚5分硬币,5枚1分硬币
- 1枚5分硬币,5枚1分硬币,1枚5分硬币
- 2枚一分硬币、1枚五分硬币、2枚一分硬币、1枚五分硬币、1枚一分硬币
以上所有例子都使用相同的硬币组合。他们都使用2枚5分硬币和5枚1分硬币,按不同顺序计数
我们可以通过始终按相同顺序发行硬币来避免上述问题。这就提出了一个简单的算法,用于计算我们可以通过多少种方式从硬币列表中进行一定数量的更改。我们可以使用第一种硬币中的一种,也可以承诺不再使用这种硬币
waysToMake 0 _ = 1
waysToMake x _ | x < 0 = 0
waysToMake x [] = 0
waysToMake x (c:cs) = waysToMake (x-c) (c:cs) + waysToMake x cs
前三个步骤似乎不太可疑,但我们已经遇到了两次WayTomake 13[2,5]
。在第四步中,我们看到了WayTomake 12[2,5]
,WayTomake 11[2,5]
,WayTomake 13[5]
,所有这些我们以前都见过。我们可以看到,我们将重复我们生成的大多数其他表达式,这些表达式本身将生成重复表达式的表达式。啊,;我有限的智能电脑已经在抱怨有太多的工作要做了。我们可以寻找更好的顺序来使用中的硬币(有一个),但它仍然会重复子问题,而子问题本身也会重复子问题,等等
快速解决问题
有一种更快的方法可以做到这一点。每一步,我们将使用较少硬币和不使用该硬币的数字相加。让我们做一个表格,在计算时记录每个结果。每一步,我们都需要表中最左边的数字(使用第一枚硬币中的一枚)和表下的一枚(不要使用第一枚硬币中的任何一枚)。我们将最终探索整个桌子。我们可以从填充边界条件覆盖的左边缘和下边缘上的数字开始
Coins\Change 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
[1,2,5] 1
[2,5] 1
[5] 1
[] 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
现在,我们将添加所有可以从表中已有的数字计算出来的数字。使用5美分硬币需要在左边的5号点和下面的1号点
Coins\Change 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
[1,2,5] 1
[2,5] 1
[5] 1 0 0 0 0 1
[] 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Coins\Change 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
[1,2,5] 1
[2,5] 1 0 1
[5] 1 0 0 0 0 1 0 0 0 0 1
[] 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Coins\Change 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
[1,2,5] 1 1
[2,5] 1 0 1 0 1
[5] 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1
[] 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
使用2美分硬币需要在左边的2号位置,以及下面的1号位置
Coins\Change 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
[1,2,5] 1
[2,5] 1
[5] 1 0 0 0 0 1
[] 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Coins\Change 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
[1,2,5] 1
[2,5] 1 0 1
[5] 1 0 0 0 0 1 0 0 0 0 1
[] 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Coins\Change 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
[1,2,5] 1 1
[2,5] 1 0 1 0 1
[5] 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1
[] 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
使用1美分硬币需要左边的1号点和下面的1号点
Coins\Change 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
[1,2,5] 1
[2,5] 1
[5] 1 0 0 0 0 1
[] 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Coins\Change 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
[1,2,5] 1
[2,5] 1 0 1
[5] 1 0 0 0 0 1 0 0 0 0 1
[] 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Coins\Change 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
[1,2,5] 1 1
[2,5] 1 0 1 0 1
[5] 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1
[] 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
我们再往前走一步。我们可以看到,在接下来的13个简单步骤中,我们将计算最上面一行中15的数字,我们就完成了
Coins\Change 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
[1,2,5] 1 1 2
[2,5] 1 0 1 0 1 1 1
[5] 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1
[] 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
这是经过所有步骤后的桌子。我的智能计算机毫不费力地计算出WayToMake 15[1,2,5]=18
Coins\Change 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
[1,2,5] 1 1 2 2 3 4 5 6 7 8 10 11 13 14 16 18
[2,5] 1 0 1 0 1 1 1 1 1 1 2 1 2 1 2 2
[5] 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1
[] 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
如果我们找到一个更好的订单来使用(有一个)中的硬币,我们不需要填写所有表格,但工作量大致相同
我们可以在Haskell中使用中的Data.Array
中的数组创建这样的表
使用表格的总体计划将是根据我们的函数WayToMake
定义一个表格。只要WayToMake
将自身递归,请在表中查找结果。我们在路上有两条皱纹要处理
第一个缺点是数组
要求数组中的索引是的实例。我们的硬币列表没有很好的数组索引。相反,我们可以用跳过的硬币数量替换硬币列表<第一行的代码>0
,第二行的代码>1
,最后一行的货币列表长度
第二个问题是,我们想看桌子边缘以外的地方