反恐搜索——打印间隔两枚选择的硬币

接前面

递归的改造——间隔挑硬币里想到了间隔两枚挑选硬币可以应用到反恐搜索。现在实现未完成的选择情况打印。
在这里插入图片描述

但是一百个硬币章节提到的,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,穷举递归应该是第一个尝试的办法。

后话

算法时间复杂度分析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值