接前面
递归的改造——间隔挑硬币里想到了间隔两枚挑选硬币可以应用到反恐搜索。现在实现未完成的选择情况打印。
但是一百个硬币章节提到的,0 ~ 99的硬币测试一个多小时还没有结果。
File "E:\python\间一个找硬币(递归改栈).py", line 220, in coins2
pick3 = coins2(row[1:], table)[0]
File "E:\python\间一个找硬币(递归改栈).py", line 213, in coins2
pick1 = coins2(row[4:], table)[0]
File "E:\python\间一个找硬币(递归改栈).py", line 222, in coins2
pick4 = coins2(row[2:], table)[0]
File "E:\python\间一个找硬币(递归改栈).py", line 213, in coins2
pick1 = coins2(row[4:], table)[0]
KeyboardInterrupt
Ctrl+C终止情况。
二十个随机数测试
row = [552, 440, 606, 818, 756, 577, 266, 312, 174, 692, 492, 635, 92, 644, 571, 51, 732, 357, 110, 545]
_, table = coins2(row,{})
print(table)
{0: 0, 1: 545, 2: 655, 3: 655, 4: 1277, 5: 1328, 6: 1328, 7: 1921, 8: 2013, 9: 2055, 10: 2455, 11: 3105, 12: 3105, 13: 3105, 14: 3371, 15: 3948, 16: 4438, 17: 4679, 18: 4795, 19: 4994, 20: 5430}
>>> _
[545, 732, 644, 692, 492, 756, 577, 552, 440]
>>> sum(_)
5430
>>>
1,从table看到最后有20枚硬币选择的时候,是【20:5430】,即总价值5430。跟返回的第一个参数“_”,的[545, 732, 644, 692, 492, 756, 577, 552, 440]加起来是一样的。所以[545, 732, 644, 692, 492, 756, 577, 552, 440]就是选择的硬币。对应反恐搜索里应该搜索的建筑。
2,row = [552, 440, 606, 818, 756, 577, 266, 312, 174, 692, 492, 635, 92, 644, 571, 51, 732, 357, 110, 545]是调试过程中生成的随机数。
3,之前是另外定义一个函数打印选择的硬币。所以第一个参数没有要。现在是直接在求最大价值的时候就求出了所选择的硬币。
把result变成一个列表
改造coins2
之前result只是一个数值,就是选择硬币的总价值。求价值直接用它在递归调用里使用。用所选择的硬币的列表也可以求出总价值。对比代码改动得很少,主要是最后max确定总价值后,要看选的是那个pick,返回对应的选择列表。
代码
#把result变成一个列表
def coins2(row, table):
if len(row) == 0:
table[0] = 0
return [0,], table
elif len(row) == 1:
table[1] = row[0]
return [row[0],], table
#选了0、1就要从4开始选
pick1 = coins2(row[4:], table)[0]
pick1.append(row[0])
pick1.append(row[1])
#只选0,可以从3开始选
pick2 = coins2(row[3:], table)[0]
pick2.append(row[0])
#0不选,就从1开始选
pick3 = coins2(row[1:], table)[0]
#0、1不选,就从2开始选
pick4 = coins2(row[2:], table)[0]
result = max(sum(pick1), sum(pick2), sum(pick3), sum(pick4))
table[len(row)] = result
if sum(pick1) == result:
return (pick1, table)
elif sum(pick2) == result:
return (pick2, table)
elif sum(pick3) == result:
return (pick3, table)
elif sum(pick4) == result:
return (pick4, table)
重复建表
每次调用都会建立四个列表。这应该就是慢的原因。可以像总价值的参数table一样,建个字典吗?
字典方式
def coins2(row, table, select={}):
if len(row) == 0:
table[0] = 0
select[0] = list((0,))
#print(type(select[0]))
return (0, table, select[0])
elif len(row) == 1:
table[1] = row[0]
select[1] = list((row[0],))
#print(type(select[1]))
return (row[0], table, select[1])
#选了0、1就要从4开始选
pick1,_,select[len(row)] = coins2(row[4:], table, select)
pick1 = pick1 + row[0] + row[1]
#只选0,可以从3开始选
pick2,_,select[len(row)] = coins2(row[3:], table, select)
pick2 += row[0]
#0不选,就从1开始选
pick3,_,select[len(row)] = coins2(row[1:], table, select)
#0、1不选,就从2开始选
pick4,_,select[len(row)] = coins2(row[2:], table, select)
result = max(pick1, pick2, pick3, pick4)
if pick1 == result:
#print(type(select[len(row)]))
select[len(row)].append(row[0])
select[len(row)].append(row[1])
elif pick2 == result:
select[len(row)].append(row[0])
#select[len(row)] = select[len(row)] + row[0]
#print(select[len(row)])
table[len(row)] = result
return (result, table, select[len(row)])
这个代码不用看,因为它是错的。不可能同种数量的硬币只有一种选法,比如选到第五个的时候,应该保存多个选法给后面求最大价值。把字典做参数,而且字典的值是列表,还是一种不错的尝试。不可以像前面间隔一枚选硬币打印的方法traceback,另起一个函数打印吗?后面把这个函数直接列出来了。
coins2traceback打印
失败的打印
Input row= [552, 440, 606, 818, 756, 577, 266, 312, 174, 692, 492, 635, 92, 644, 571, 51, 732, 357, 110, 545]
Table = {0: 0, 1: 545, 2: 655, 3: 655, 4: 1277, 5: 1328, 6: 1328, 7: 1921, 8: 2013, 9: 2055, 10: 2455, 11: 3105, 12: 3105, 13: 3105, 14: 3371, 15: 3948, 16: 4438, 17: 4679, 18: 4795, 19: 4994, 20: 5430}
Selected coins are [552, 440, 756, 577, 492, 635, 732] and sum up to 5430
[552, 440, 756, 577, 492, 635, 732] 与前面正确的
[545, 732, 644, 692, 492, 756, 577, 552, 440]不一样。最大价值是没有问题,都是5430。
参照求价值的四种情形
def coins2traceback(row, table):#待完成
select = []
i = 0#从后面复盘路径
while i < len(row):
print("in",i)
#pick1 选了0、1就要从4开始选
if ((len(row)>=i+4)and(table[len(row)-i] - table[len(row)-i-4] == row[i] + row[i+1])):
select.append(row[i])
select.append(row[i+1])
print("pick1 选了0、1就要从4开始选",table[len(row)-i],table[len(row)-i-4])
i += 4
#pick2 只选0,可以从3开始选
elif ((len(row)>=i+3)and(table[len(row)-i] - table[len(row)-i-3] == row[i])):
select.append(row[i])
print("pick2 只选0,可以从3开始选",table[len(row)-i],table[len(row)-i-4])
i += 3
#pick4 0、1不选,就从2开始选
elif ((len(row)>=i+2)and(table[len(row)-i] - table[len(row)-i-1] != row[i])and
(table[len(row)-i] - table[len(row)-i-1] != row[i+1])):
print("pick4 0、1不选,就从2开始选",table[len(row)-i],table[len(row)-i-4])
i += 2
else:
i += 1
print('Input row=', row)
print('Table =', table)
print('Selected coins are', select, 'and sum up to', table[len(row)])
这个代码也是错的。前面的coins2里是有四种选择pick1、pick2、pick3、pick4,再比较选择最大价值的一个。间隔一枚选硬币打印的方法traceback是直接从后面推导的。
倒着推
主要的判断分支如下:
if ((len(row)>=i+2)and (table[len(row)-i] == table[len(row)-i-1] + row[i])and
(table[len(row)-i-1] == table[len(row)-i-2] + row[i+1])):#选了两个跳过两个+4
select.append(row[i])
select.append(row[i+1])
print('选了两个跳过两个+4',table[len(row)-i-2],table[len(row)-i-1],table[len(row)-i])
i += 4
#选两个比,刚才间隔两个选得好#+4,第四个还要再考虑
elif ((len(row)>=i+4)and(table[len(row)-i-1] == row[i+1] + row[i+4]) and
(table[len(row)-i] == table[len(row)-i-1] + row[i])):
#选两个隔两个又选了一个,比刚才连选的两个好
select.append(row[i])
select.append(row[i+1])
print("选两个比,刚才间隔两个选得好",table[len(row)-i-2],table[len(row)-i-1],table[len(row)-i])
i += 4
elif ((len(row)>=i+4)and(table[len(row)-i] != table[len(row)-i-1]) and
(table[len(row)-i] - table[len(row)-i-1] == row[i]+row[i+4] - row[i+2] )):
#选两个隔两个又选了一个,比刚才连选得两个好
select.append(row[i])
select.append(row[i+1])
select.append(row[i+4])
print("选两个隔两个又选了一个",table[len(row)-i-2],table[len(row)-i-1],table[len(row)-i])
i += 5
elif ((table[len(row)-i] != table[len(row)-i-1]) and #第一个是0的时候没有考虑到
(table[len(row)-i] == table[len(row)-i-1] + row[i])):#选了一个跳两个+3
select.append(row[i])
print(i,table[len(row)-i-1],table[len(row)-i])
i += 3
#选两个间隔两个从前面选的#+4,第四个还要再考虑
elif ((len(row)>=i+4)and(table[len(row)-i] - table[len(row)-i-4] == row[i] + row[i+1])):
select.append(row[i])
select.append(row[i+1])
print("选两个间隔两个从前面选的",table[len(row)-i],table[len(row)-i-4])
i += 4
else:#没选
i += 1
这段代码也是错的,应该还有更多的情况没有考虑到。
偶然成功的打印
in 0
in 1
1 118 145 148
in 5
5 13 111 118
Input row= [14, 3, 27, 4, 5, 15, 100, 11, 2, 3]
Table = {0: 0, 1: 3, 2: 5, 3: 13, 4: 111, 5: 118, 6: 118, 7: 118, 8: 145, 9: 148, 10: 148}
Selected coins are [3, 27, 15, 100, 3] and sum up to 148
这个还是在前面硬币的基础上加了11,2,3几个数测试的。直接用 [14, 3, 27, 4, 5, 15, 100]测试也是可以的。在进行随机数测试的时候就不行了。前面的那20个随机数就是这时生成的。这样的倒推也没有那么简单。我又试了间隔一枚的打印,它就是可以的。
回溯
def traceback(row, table):
'''打印选择的硬币
Arguments:
row:硬币列表
table:记录所选硬币总金额的字典
Return:
打印选择的硬币select
'''
select = []
i = 0#从后面复盘路径
while i < len(row):
if (table[len(row)-i] == row[i])\
or(table[len(row)-i] ==\
table[len(row)-i-2] + row[i]):
select.append(row[i])
i += 2
else:
i += 1
print('Selected coins are', select, 'and sum up to', table[len(row)])
它就是写得这么简单,从后面横向走、纵向走。回溯——3、5升杯倒4升水把书上回溯的基本步骤对应上,一定可以的啊,只是多间隔一枚。
总结
1,调式的时候发现前面文章写的coins2Iterative,间隔两枚选硬币的迭代方法是错的,还有没考虑到的情况。
2,前面文章写递归调用的coins2直接选择0~99枚硬币也是长时间没有运行结果。
3,实际我是从后面一步一步尝试到最前面才成功。这文章应该倒着看。
4,一个函数里4次调用自己,而且问题规模没有太大的缩小,复杂度应该是很高的。
4,穷举递归应该是第一个尝试的办法。
后话
算法时间复杂度分析