Python 贪婪递归算法的时间复杂度
我编写了一个贪婪的递归算法,以找到进行给定更改的最小硬币数。现在我需要估计它的时间复杂度。由于算法根据相同的i(n*n)嵌套了“ifs”,内部块将递归调用(log(2)n)减半,我相信正确的答案可能是O(n*log(n)),这是由以下计算得出的: n*log2(n)*O(1) 请告诉我您对我的分析是否正确的想法,并对我的贪婪递归算法提出改进建议 这是我的递归算法:Python 贪婪递归算法的时间复杂度,python,recursion,time-complexity,big-o,greedy,Python,Recursion,Time Complexity,Big O,Greedy,我编写了一个贪婪的递归算法,以找到进行给定更改的最小硬币数。现在我需要估计它的时间复杂度。由于算法根据相同的i(n*n)嵌套了“ifs”,内部块将递归调用(log(2)n)减半,我相信正确的答案可能是O(n*log(n)),这是由以下计算得出的: n*log2(n)*O(1) 请告诉我您对我的分析是否正确的想法,并对我的贪婪递归算法提出改进建议 这是我的递归算法: coins = [1, 5, 10, 21, 25] coinsArraySize = len(coins) change = 63
coins = [1, 5, 10, 21, 25]
coinsArraySize = len(coins)
change = 63
pickedCoins = []
def findMin(change, i, pickedCoins):
if (i>=0):
if (change >= coins[i]):
pickedCoins.append(coins[i])
findMin(change - coins[i], i, pickedCoins)
else:
findMin(change, i-1, pickedCoins)
findMin(change, coinsArraySize-1, pickedCoins)
什么是
n
?运行时间取决于金额和具体硬币。例如,假设您有一百万个硬币,从1到1000000,并尝试为1进行更改。在最终找到可以使用的最大硬币(1)之前,代码将经历一百万个递归级别。最后,如果你只有一枚硬币()并试图兑换1000000美元,那么你会立刻找到那枚硬币,但要花一百万次的时间去深拣那枚硬币
这是一个非递归版本,它改进了这两个方面:使用二进制搜索查找下一个可用的硬币,一旦找到合适的硬币,就尽可能多地使用它
def makechange(amount, coins):
from bisect import bisect_right
# assumes `coins` is sorted. and that coins[0] > 0
right_bound = len(coins)
result = []
while amount > 0:
# Find largest coin <= amount.
i = bisect_right(coins, amount, 0, right_bound)
if not i:
raise ValueError("don't have a coin <=", amount)
coin = coins[i-1]
# How many of those can we use?
n, amount = divmod(amount, coin)
assert n >= 1
result.extend([coin] * n)
right_bound = i - 1
return result
这本质上是O(n+n log n)
其中n
是len(硬币)
寻找最优解
正如@Stef在一篇评论中指出的,贪婪算法并不总能找到最少数量的硬币。这要难得多。通常的方法是通过,最坏的情况是O(amt*len(coins))
time。但这也是最好的情况:它“自下而上”工作,找到最小数量的硬币,达到1,然后2,然后3,然后4,…,最后amt
def mincoins(amt, coins):
from collections import deque
coins = sorted(set(coins)) # increasing, no duplicates
# Map amount to coin that was subtracted to reach it.
a2c = {amt : None}
d = deque([amt])
while d:
x = d.popleft()
for c in coins:
y = x - c
if y < 0:
break # all remaining coins too large
if y in a2c:
continue # already found cheapest way to get y
a2c[y] = c
d.append(y)
if not y: # done!
d.clear()
break
if 0 not in a2c:
raise ValueError("not possible", amt, coins)
picks = []
a = 0
while True:
c = a2c[a]
if c is None:
break
picks.append(c)
a += c
assert a == amt
return sorted(picks)
因此,我将建议一种不同的方法,使用广度优先树搜索,从初始数量向下计算,直到达到0。最坏情况下的O()
行为是相同的,但最佳情况下的时间要好得多。对于评论:
mincoins(10000, [1, 2000, 3000])
这种情况下,在找到最佳的4硬币解决方案之前,它会查看不到20个节点。因为这是一个广度优先的搜索,它知道不可能有更短的路径到达根,所以可以立即停止
举个最坏的例子,试试看
mincoins(1000001, range(2, 200, 2))
所有的硬币都是偶数,所以不可能收集到任何一个硬币的总和都是奇数。树必须扩展到50万层深才能意识到0是不可访问的。但是,虽然高级别的分支因子是O(len(coins))
,但整个扩展树中的节点总数以amt+1为界(鸽子洞原则:dict最多只能有amt+1
键,因此超出该值的任何数量的节点都必然是重复的目标,因此在生成后都会被丢弃)。因此,实际上,在这种情况下,树很快变宽,但很快就会变窄,变深
还要注意的是,这种方法可以很容易地重新构建总计为amt
的最小硬币集合
def mincoins(amt, coins):
from collections import deque
coins = sorted(set(coins)) # increasing, no duplicates
# Map amount to coin that was subtracted to reach it.
a2c = {amt : None}
d = deque([amt])
while d:
x = d.popleft()
for c in coins:
y = x - c
if y < 0:
break # all remaining coins too large
if y in a2c:
continue # already found cheapest way to get y
a2c[y] = c
d.append(y)
if not y: # done!
d.clear()
break
if 0 not in a2c:
raise ValueError("not possible", amt, coins)
picks = []
a = 0
while True:
c = a2c[a]
if c is None:
break
picks.append(c)
a += c
assert a == amt
return sorted(picks)
def mincoins(金额,硬币):
从集合导入deque
硬币=分类(设置(硬币))#增加,无重复
#地图上的数字等于减去后的硬币数。
a2c={amt:None}
d=deque([amt])
而d:
x=d.popleft()
对于硬币中的c:
y=x-c
如果y<0:
打破#所有剩余的硬币都太大了
如果a2c中为y:
继续#已经找到了最便宜的方式获得y
a2c[y]=c
d、 附加(y)
如果不是y:#完成!
d、 清除()
打破
如果0不在a2c中:
提高值错误(“不可能”,金额,硬币)
选择=[]
a=0
尽管如此:
c=a2c[a]
如果c为无:
打破
picks.append(c)
a+=c
断言a==amt
返回已排序(拾取)
每个递归调用将更改至少减少1,并且没有分支(也就是说,您的递归树实际上是一条直线,因此实际上不需要递归)。您的运行时间是O(n)
没那么简单:其他分支根本不改变改变
;相反,它减少了i
。一个普通的n
在这里没有意义,因为改变
和硬币数量都在起作用。@TimPeters是,也不是。因为OP要求运行时间作为的函数de>n
,我假设硬币的数量是固定的,这意味着它只影响常数。@IgorRivin如果你将此算法转换为迭代算法,它将有嵌套的循环,例如,这些ifs可能会变成whiles。我看不到这个问题的复杂性更低。@PedroCoelho“内部循环”将最多运行不同硬币的数量,这是一个常量(问题的措辞)。如果硬币的数量也是一个输入(称之为m
),则有一个明显的O(nm)
bound,常数取决于硬币的面额。但是,除非硬币的数量和金额有某种关联,否则我看不到任何日志术语。谢谢你的透彻解释,蒂姆。考虑到硬币数组是[1,5,10,21,25],复杂度会是多少变化是63?考虑到在这个问题上我们已经有了这些信息,只有一种复杂性,而不是最坏和最好的情况,对吗?再一次,什么是n
?如果所有的输入都是固定的,没有什么变化。它需要花费任何时间,最坏、最好和平均的情况都是一样的-只有一种情况;-)如果你保持硬币固定并改变传入的数量,这就是n
,那么我的算法最坏的情况是O(n)
,但是如果你使用dict输出格式,最坏的情况是O(log2(n))
。实际上,如果硬币集是固定的,那么dic