递归的改造——间隔挑硬币

递归的改造

接前面

把前面写的汉诺塔递归函数改成非递归方式。这只是个开场白,他们享受的是慢时光,寺庙又不加班。这个故事只是叫我们敬畏智慧,沉下心来。

递归函数都可以转成非递归函数

书上说所有的递归调用都可以改成非递归调用的方式。那是编译器,解释器的工作。计算机是用栈处理递归的,但处理深度有限。自己改没说一定要用栈。比如兔子数列
在这里插入图片描述

兔子数列

这里还使用了存储的方法,提高运行速度。

def memorizeFib(x):
    memo = [0, 1, 1, 2, 3]
    if len(memo)-1 >= x:
        return memo[x]
    else:
        while len(memo)-1 < x:
            memo.append(memo[-1] + memo[-2])
        return memo[x]

非递归快速排序

离智慧再近一点,‘非递归快速排序’1500个数据就出现调用深度报错。改成后进先出的栈就可以了。不用换成资源更好的电脑,搏一搏单车当摩托。

广度优先

广度优先里是没有递归调用的,比如红黑树转2-3-4树——雨后春笋的层序打印,层序打印比递归的前中后根更直观。在改造递归调用的时候可以考虑一下广度优先。

间隔一个选硬币

硬币价值[14,3,27,4,5,15,1,],一次最多只能拿一枚,就是要间隔至少一枚才能拿后面的,应该怎么选?

递归调用法

def coins(row, table):
    '''至少隔一个才能选的硬币
    Arguments:
        row:硬币列表
        table:记录所选硬币总金额的字典
    Return:
        result:所选最大金额
        table:记录所选硬币总金额的字典,用于打印选择的路径
    '''
    if len(row) == 0:
        table[0] = 0
        return 0, table
    elif len(row) == 1:
        table[1] = row[0]
        return row[0], table
    #第0个选后跳一个选row[2:]
    pick = coins(row[2:],table)[0] + row[0]
    #第0个跳过选row[1:]
    skip = coins(row[1:],table)[0]
    result = max(pick, skip)
    table[len(row)] = result
    return result, table

打印

>>>print(coins(row, {}))
(56, {1: 1, 0: 0, 2: 15, 3: 15, 4: 19, 5: 42, 6: 42, 7: 56})

可以了吗?似乎总有点不放心,把1改成100试试。
(146, {1: 100, 0: 0, 2: 100, 3: 105, 4: 105, 5: 132, 6: 132, 7: 146})
观察打印的字典会发现它是从后面往前面选的。递归真是个叛离的孩子。

递归与回溯

回溯打印选择过程

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)])
>>> result,table = coins(row,{})
>>> traceback(row, table)
Selected coins are [14, 27, 5, 100] and sum up to 146

存储中间变量改造

跟前面的兔子数列一样,存储中间变量改造递归调用。

def coinsMemorize(row, memo):
    print('this is len(memo)',len(memo))
    if len(row) == 0:
        memo[0] = 0
        return (0, memo)
    elif len(row) == 1:
        memo[1] = row[0]
        return (row[0], memo)
    try:
        return (memo[len(row)], memo)
    except KeyError:
        pick = coinsMemorize(row[2:], memo)[0] + row[0]
        skip = coinsMemorize(row[1:], memo)[0]
        result = max(pick, skip)
        memo[len(row)] = result
        return (result, memo)

递归改造成迭代

1,用字典table类似中间变量的增加来选。
2,考虑table0,1的基础情况。
3,参照前面递归的从后面选。

def coinsIterative(row):
    '''至少隔一个才能选的硬币,从后往前
    Arguments:
        row:硬币列表
    Return:
        result:所选最大金额
        table:记录所选硬币总金额的字典,是从后往前的。        
    '''
    table = {}
    table[0] = 0
    table[1] = row[-1]
    for i in range(2, len(row)+1):
        skip = table[i-1]
        pick = table[i-2] + row[-i]
        result = max(pick, skip)
        table[i] = result
    return table[len(row)], table    
>>> _, table = coinsIterative(row)
traceback(row, table)(146, {0: 0, 1: 100, 2: 100, 3: 105, 4: 105, 5: 132, 6: 132, 7: 146})
Selected coins are [14, 27, 5, 100] and sum up to 146

能从前面选吗?

递归调用与回溯

把-1改成0,-i改成i-1

def coinsIterative2(row):
    '''至少隔一个才能选的硬币
    Arguments:
        row:硬币列表
    Return:
        result:所选最大金额
    '''
    table = {}
    table[0] = 0
    table[1] = row[0]
    for i in range(2, len(row)+1):
        skip = table[i-1]
        pick = table[i-2] + row[i-1]
        result = max(pick, skip)
        table[i] = result
    return table[len(row)], table
>>> print('coinsIterative:',coinsIterative2(row))
coinsIterative: (146, {0: 0, 1: 14, 2: 14, 3: 41, 4: 41, 5: 46, 6: 56, 7: 146})
>>> 

真的可以!
原来就是先选一个;第二个大就改第二个;第三步可能用第二步的,也可能是第三个加第一步的;后面都是用前一步或者前前步加一个。如果从中间看书的,一定不会再往前面看了。
停一下,感受僧人的智慧。他们一定知道所有的细节,比如汉诺塔的移动次数是 2 n − 1 2^n-1 2n1
是否可以在max选择的时候直接打印路径,我试了,但是打印不了。应该是选来选去,中间会有很多变化,还是要回溯好。看来要回溯的时候,递归是首选。
递归调用与回溯

间隔两个硬币

现在最多可以连续选两枚硬币,但是选了之后要至少间隔两枚硬币。

递归调用

还是先看递归调用方法。

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] + row[0] + row[1]
    #只选0,可以从3开始选
    pick2 = coins2(row[3:], table)[0] + row[0]
    #0不选,就从1开始选
    pick3 = coins2(row[1:], table)[0]
    #0、1不选,就从2开始选
    pick4 = coins2(row[2:], table)[0]    
    result = max(pick1, pick2, pick3, pick4)
    table[len(row)] = result
    return (result, table)
>>> print('coinsIterative:',coins2(row,{}))
coinsIterative: (145, {0: 0, 1: 100, 2: 115, 3: 115, 4: 115, 5: 142, 6: 145, 7: 145})
>>> 

推导

重点。

    elif len(row) == 2:#一次最多连续选择两枚,下次选择至少跳过两枚,选两个跳两个吧
        table[2] = row[0] + row[1]
        return table[2], table

    elif len(row) == 3:
        table[3] = max(row[0]+row[1],row[1]+row[2])
        return table[3], table
    elif len(row) == 4:
        table[4] = max(row[0]+row[1],row[1]+row[2],row[2]+row[3],row[0]+row[3])
        return table[4], table    
    elif len(row) == 5:
        table[5] = max(row[0]+row[1]+row[4],row[1]+row[2],row[2]+row[3],
                       row[3]+row[4],row[0]+row[3]+row[4])
        return table[5], table
    elif len(row) == 6:
        table[6] = max(row[0]+row[1]+row[4]+row[5],row[1]+row[2]+row[5],row[2]+row[3],
                       row[0]+row[3]+row[4])
        return table[6], table
    elif len(row) == 7:
        table[7] = max(row[0]+row[1]+row[4]+row[5],row[0]+row[1]+row[5]+row[6],
                       row[0]+row[3]+row[4],row[0]+row[4]+row[5],row[0]+row[5]+row[6],
                       row[1]+row[2]+row[5]+row[6],row[2]+row[3]+row[6])
        return table[7], table

我是把7种情况都写了,再参照前面间隔一枚硬币的迭代方法。

def coins2Iterative(row):
    table = {}
    table[0] = 0
    table[1] = row[0]
    table[2] = row[0] + row[1]
    table[3] = max(table[2], row[2] + row[1])
    table[4] = max(table[3], row[3] + row[2],
                   row[3] + table[1])
    table[5] = max(table[4], row[4] + row[3] + table[1],
                 row[4] + table[2])
    table[6] = max(table[5] , row[5] + row[4] + table[2],
                 row[5] + table[3])
    for i in range(5, len(row) + 1):
        #print(i)
        table[i] = max(table[i-1],row[i-1] + row[i-2] + table[i-4],
                       row[i-1] + table[i-2])
    return table[len(row)], table

一百个硬币

间隔两枚所选择的硬币打印还没有完成。可以打印选择的总价值。

>>> print('一百个硬币:',coins2Iterative(list(range(100))))
一百个硬币: (2525, {0: 0, 1: 0, 2: 1, 3: 3, 4: 5, 5: 7, 6: 10, 7: 14, 8: 18, 9: 22, 10: 27, 11: 33, 12: 39, 13: 45, 14: 52, 15: 60, 16: 68, 17: 76, 18: 85, 19: 95, 20: 105, 21: 115, 22: 126, 23: 138, 24: 150, 25: 162, 26: 175, 27: 189, 28: 203, 29: 217, 30: 232, 31: 248, 32: 264, 33: 280, 34: 297, 35: 315, 36: 333, 37: 351, 38: 370, 39: 390, 40: 410, 41: 430, 42: 451, 43: 473, 44: 495, 45: 517, 46: 540, 47: 564, 48: 588, 49: 612, 50: 637, 51: 663, 52: 689, 53: 715, 54: 742, 55: 770, 56: 798, 57: 826, 58: 855, 59: 885, 60: 915, 61: 945, 62: 976, 63: 1008, 64: 1040, 65: 1072, 66: 1105, 67: 1139, 68: 1173, 69: 1207, 70: 1242, 71: 1278, 72: 1314, 73: 1350, 74: 1387, 75: 1425, 76: 1463, 77: 1501, 78: 1540, 79: 1580, 80: 1620, 81: 1660, 82: 1701, 83: 1743, 84: 1785, 85: 1827, 86: 1870, 87: 1914, 88: 1958, 89: 2002, 90: 2047, 91: 2093, 92: 2139, 93: 2185, 94: 2232, 95: 2280, 96: 2328, 97: 2376, 98: 2425, 99: 2475, 100: 2525})

反恐应用

无人机已扫描所有的建筑,总台给出了每栋建筑可疑人员的概率。但兵力有限最多只能连续搜索两栋,还要跳过两栋以免打草惊蛇。情况紧急,必须在天亮之前赶到下一战区。

后话

深广度优先
回溯方法
间隔两枚的打印

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值