344 lines
9.3 KiB
Markdown
344 lines
9.3 KiB
Markdown
# 原创
|
||
: Python数据结构(四)——递归
|
||
|
||
# Python数据结构(四)——递归
|
||
|
||
# 递归
|
||
|
||
递归是一种解决问题的方法,将问题分解为更小的子问题,直到得到一个足够小的问题可以被很简单的解决。通常递归涉及函数调用自身。递归允许我们编写优雅的解决方案,解决可能很难编程的问题。
|
||
|
||
## 计算整数列表和
|
||
|
||
```
|
||
# version1
|
||
def list_sum(num_list):
|
||
num_sum = 0
|
||
for i in num_list:
|
||
num_sum += i
|
||
return num_sum
|
||
print list_sum([1,3,5,7,9])
|
||
```
|
||
|
||
```
|
||
25
|
||
|
||
```
|
||
|
||
```
|
||
# version2:不使用循环
|
||
def list_num2(num_list):
|
||
if len(num_list)==1:
|
||
return num_list[0]
|
||
else:
|
||
return num_list[0]+list_num2(num_list[1:])
|
||
print list_num2([1,3,5,7,9])
|
||
|
||
|
||
```
|
||
|
||
```
|
||
25
|
||
|
||
```
|
||
|
||
## 递归三定律
|
||
|
||
## 整数转任意进制字符
|
||
|
||
```
|
||
def to_str(n,base):
|
||
conver_string = "0123456789ABCDEF"
|
||
if n < base:
|
||
return conver_string[n]
|
||
else:
|
||
return str(to_str(n//base,base))+conver_string[n%base]
|
||
print to_str(1453,16)
|
||
```
|
||
|
||
```
|
||
5AD
|
||
|
||
```
|
||
|
||
## 栈帧:实现递归
|
||
|
||
```
|
||
from pythonds.basic.stack import Stack
|
||
rStack = Stack()
|
||
|
||
def to_str(n,base):
|
||
conver_string = "0123456789ABCDEF"
|
||
while n>0:
|
||
if n<base:
|
||
rStack.push(conver_string[n])
|
||
else:
|
||
rStack.push(conver_string[n%base])
|
||
n //= base
|
||
res = ""
|
||
while not rStack.isEmpty():
|
||
res += str(rStack.pop())
|
||
return res
|
||
|
||
print to_str(1453,16)
|
||
```
|
||
|
||
```
|
||
5AD
|
||
|
||
```
|
||
|
||
## 可视化递归
|
||
|
||
```
|
||
# turtle 是 Python 所有版本的标准库,插图的工具
|
||
import turtle
|
||
```
|
||
|
||
```
|
||
myTurtle = turtle.Turtle()
|
||
myWin = turtle.Screen()
|
||
|
||
def drawSpiral(myTurtle,linelen):
|
||
if linelen > 0:
|
||
# 直走
|
||
myTurtle.forward(linelen)
|
||
# 右拐
|
||
myTurtle.right(90)
|
||
# 递归
|
||
drawSpiral(myTurtle,linelen)
|
||
drawSpiral(myTurtle,100)
|
||
# 调用函数 myWin.exitonclick(),这是一个方便的缩小窗口的方法,
|
||
# 使乌龟进入等待模式,直到你单击窗口,然后程序清理并退出。
|
||
myWin.exitonclick()
|
||
```
|
||
|
||
```
|
||
# 绘制分型树
|
||
import turtle
|
||
def tree(branchlen,t):
|
||
if branchlen > 5:
|
||
t.forward(branchlen)
|
||
t.right(20)
|
||
tree(branchlen-15,t)
|
||
t.left(40)
|
||
tree(branchlen-10,t)
|
||
t.right(20)
|
||
t.backward(branchlen)
|
||
def main():
|
||
t = turtle.Turtle()
|
||
myWin = turtle.Screen()
|
||
t.left(90)
|
||
t.up()
|
||
t.backward(100)
|
||
t.down()
|
||
t.color("green")
|
||
tree(75,t)
|
||
myWin.exitonclick()
|
||
main()
|
||
```
|
||
|
||
## 谢尔宾斯基三角形
|
||
|
||
```
|
||
import turtle
|
||
|
||
def drawTriangle(points,color,myTurtle):
|
||
myTurtle.fillcolor(color)
|
||
myTurtle.up()
|
||
myTurtle.goto(points[0][0],points[0][1])
|
||
myTurtle.down()
|
||
myTurtle.begin_fill()
|
||
myTurtle.goto(points[1][0],points[1][1])
|
||
myTurtle.goto(points[2][0],points[2][1])
|
||
myTurtle.goto(points[0][0],points[0][1])
|
||
myTurtle.end_fill()
|
||
|
||
def getMid(p1,p2):
|
||
return ( (p1[0]+p2[0]) / 2, (p1[1] + p2[1]) / 2)
|
||
|
||
def sierpinski(points,degree,myTurtle):
|
||
colormap = ['blue','red','green','white','yellow',
|
||
'violet','orange']
|
||
drawTriangle(points,colormap[degree],myTurtle)
|
||
if degree > 0:
|
||
sierpinski([points[0],
|
||
getMid(points[0], points[1]),
|
||
getMid(points[0], points[2])],
|
||
degree-1, myTurtle)
|
||
sierpinski([points[1],
|
||
getMid(points[0], points[1]),
|
||
getMid(points[1], points[2])],
|
||
degree-1, myTurtle)
|
||
sierpinski([points[2],
|
||
getMid(points[2], points[1]),
|
||
getMid(points[0], points[2])],
|
||
degree-1, myTurtle)
|
||
|
||
def main():
|
||
myTurtle = turtle.Turtle()
|
||
myWin = turtle.Screen()
|
||
myPoints = [[-100,-50],[0,100],[100,-50]]
|
||
sierpinski(myPoints,3,myTurtle)
|
||
myWin.exitonclick()
|
||
|
||
main()
|
||
```
|
||
|
||
## 汉诺塔游戏
|
||
|
||
有一个印度教寺庙,将谜题交给年轻的牧师。在开始的时候,牧师们被给予三根杆和一堆 64 个金碟,每个盘比它下面一个小一点。他们的任务是将所有 64 个盘子从三个杆中一个转移到另一个。有两个重要的约束,它们一次只能移动一个盘子,并且它们不能在较小的盘子顶部上放置更大的盘子。牧师日夜不停每秒钟移动一块盘子。当他们完成工作时,传说,寺庙会变成灰尘,世界将消失。
|
||
|
||
虽然传说是有趣的,你不必担心世界不久的将来会消失。移动 64 个盘子的塔所需的步骤数是 2^64 -1 = 18,446,744,073,709,551,615264-1 = 18,446,744,073,709,551,615。以每秒一次的速度,即584,942,417,355584,942,417,355 年!。
|
||
|
||
这里是如何使用中间杆将塔从起始杆移动到目标杆的步骤:
|
||
|
||
```
|
||
# 一开始盘子都在起始杆上
|
||
# Python 提供了我们需要调用的隐含的栈。
|
||
def moveTower(height,fromPole, toPole, withPole):
|
||
if height >= 1:
|
||
moveTower(height-1,fromPole,withPole,toPole)
|
||
moveDisk(fromPole,toPole)
|
||
moveTower(height-1,withPole,toPole,fromPole)
|
||
def moveDisk(fp,tp):
|
||
print("moving disk from",fp,"to",tp)
|
||
moveTower(4,'fromPole', 'toPole', 'withPole')
|
||
```
|
||
|
||
```
|
||
('moving disk from', 'fromPole', 'to', 'withPole')
|
||
('moving disk from', 'fromPole', 'to', 'toPole')
|
||
('moving disk from', 'withPole', 'to', 'toPole')
|
||
('moving disk from', 'fromPole', 'to', 'withPole')
|
||
('moving disk from', 'toPole', 'to', 'fromPole')
|
||
('moving disk from', 'toPole', 'to', 'withPole')
|
||
('moving disk from', 'fromPole', 'to', 'withPole')
|
||
('moving disk from', 'fromPole', 'to', 'toPole')
|
||
('moving disk from', 'withPole', 'to', 'toPole')
|
||
('moving disk from', 'withPole', 'to', 'fromPole')
|
||
('moving disk from', 'toPole', 'to', 'fromPole')
|
||
('moving disk from', 'withPole', 'to', 'toPole')
|
||
('moving disk from', 'fromPole', 'to', 'withPole')
|
||
('moving disk from', 'fromPole', 'to', 'toPole')
|
||
('moving disk from', 'withPole', 'to', 'toPole')
|
||
|
||
```
|
||
|
||
## 动态规划
|
||
|
||
假设你是一个自动售货机制造商的程序员。你的公司希望通过给每个交易最少硬币来简化工作。假设客户放入 1 美元的钞票并购买 37 美分的商品。你可以用来找零的最小数量的硬币是多少?
|
||
|
||
假设你的公司决定在埃尔博尼亚部署自动贩卖机,除了通常的 1,5,10 和 25 分硬币,他们还有一个 21 分硬币 。
|
||
|
||
```
|
||
def recMC(coinValueList,change):
|
||
minCoins = change
|
||
if change in coinValueList:
|
||
return 1
|
||
else:
|
||
for i in [c for c in coinValueList if c <= change]:
|
||
numCoins = 1 + recMC(coinValueList,change-i)
|
||
if numCoins < minCoins:
|
||
minCoins = numCoins
|
||
return minCoins
|
||
print recMC([1,5,10,25],63)
|
||
```
|
||
|
||
```
|
||
6
|
||
|
||
```
|
||
|
||
这种算法是非常低效的。事实上,它需要 67,716,925 个递归调用来找到 4 个硬币的最佳解决 63 美分问题的方案。
|
||
|
||
减少我们工作量的关键是记住一些过去的结果,这样我们可以避免重新计算我们已经知道的结果。一个简单的解决方案是将最小数量的硬币的结果存储在表中。
|
||
|
||
```
|
||
def recMC(coinValueList,change,knownResults):
|
||
minCoins = change
|
||
if change in coinValueList:
|
||
knownResults[change] = 1
|
||
return 1
|
||
elif knownResults[change] > 0:
|
||
return knownResults[change]
|
||
else:
|
||
for i in [c for c in coinValueList if c <= change]:
|
||
numCoins = 1 + recMC(coinValueList,change-i,knownResults)
|
||
if numCoins < minCoins:
|
||
minCoins = numCoins
|
||
knownResults[change] = minCoins
|
||
return minCoins
|
||
print recMC([1,5,10,25],63,[0]*64)
|
||
```
|
||
|
||
```
|
||
6
|
||
|
||
```
|
||
|
||
这个修改的算法减少了我们需要为四个硬币递归调用的数量,63美分问题只需 221 次调用!
|
||
|
||
使用动态规划算法:
|
||
|
||
```
|
||
def dpMakeChange(coinValueList,change,minCoins):
|
||
for cents in range(change+1):
|
||
coinCount = cents
|
||
for j in [c for c in coinValueList if c<= cents]:
|
||
if minCoins[cents-j]+1<coinCount:
|
||
coinCounts = minCoins[cents-j]+1
|
||
minCoins[cents] = coinCount
|
||
return minCoins[change]
|
||
```
|
||
|
||
```
|
||
# 跟踪使用的硬币
|
||
def dpMakeChange(coinValueList,change,minCoins,coinsUsed):
|
||
for cents in range(change+1):
|
||
coinCount = cents
|
||
newCoin = 1
|
||
for j in [c for c in coinValueList if c <= cents]:
|
||
if minCoins[cents-j] + 1 < coinCount:
|
||
coinCount = minCoins[cents-j]+1
|
||
newCoin = j
|
||
minCoins[cents] = coinCount
|
||
coinsUsed[cents] = newCoin
|
||
return minCoins[change]
|
||
|
||
def printCoins(coinsUsed,change):
|
||
coin = change
|
||
while coin > 0:
|
||
thisCoin = coinsUsed[coin]
|
||
print thisCoin
|
||
coin = coin - thisCoin
|
||
|
||
def main():
|
||
amnt = 63
|
||
clist = [1,5,10,21,25]
|
||
coinsUsed = [0]*(amnt+1)
|
||
coinCount = [0]*(amnt+1)
|
||
|
||
print("Making change for",amnt,"requires")
|
||
print(dpMakeChange(clist,amnt,coinCount,coinsUsed),"coins")
|
||
print("They are:")
|
||
printCoins(coinsUsed,amnt)
|
||
print("The used list is as follows:")
|
||
print(coinsUsed)
|
||
```
|
||
|
||
```
|
||
main()
|
||
```
|
||
|
||
```
|
||
('Making change for', 63, 'requires')
|
||
(3, 'coins')
|
||
They are:
|
||
21
|
||
21
|
||
21
|
||
The used list is as follows:
|
||
[1, 1, 1, 1, 1, 5, 1, 1, 1, 1, 10, 1, 1, 1, 1, 5, 1, 1, 1, 1, 10, 21, 1, 1, 1, 25, 1, 1, 1, 1, 5, 10, 1, 1, 1, 10, 1, 1, 1, 1, 5, 10, 21, 1, 1, 10, 21, 1, 1, 1, 25, 1, 10, 1, 1, 5, 10, 1, 1, 1, 10, 1, 10, 21]
|
||
|
||
```
|