递归的改造
目录名称
接前面
把前面写的汉诺塔递归函数改成非递归方式。这只是个开场白,他们享受的是慢时光,寺庙又不加班。这个故事只是叫我们敬畏智慧,沉下心来。
递归函数都可以转成非递归函数
书上说所有的递归调用都可以改成非递归调用的方式。那是编译器,解释器的工作。计算机是用栈处理递归的,但处理深度有限。自己改没说一定要用栈。比如兔子数列
兔子数列
这里还使用了存储的方法,提高运行速度。
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
2n−1。
是否可以在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})
反恐应用
无人机已扫描所有的建筑,总台给出了每栋建筑可疑人员的概率。但兵力有限最多只能连续搜索两栋,还要跳过两栋以免打草惊蛇。情况紧急,必须在天亮之前赶到下一战区。
后话
深广度优先
回溯方法
间隔两枚的打印