概念 : 递归精髓在于,将问题分解为规模更小的相同问题,持续分解,直到问题规模小到可以用非常简单直接的方式来解决。
递归的问题分解方式非常独特,其算法方面的明显特征就是:在算法流程中调用自身。
递归为我们提供了一种对复杂问题的优雅解决方案,精妙的递归算法常会出奇简单
example:数列求和
def listsum(numlist):
if len(numlist)==1:
return numlist[0]
else:
return numlist[0]+listsum(numlist[1:]) # 调用自身
为了像阿西莫夫的“机器人三定律”致敬,递归算法也总结出“三定律”
- 递归算法必须能有一个基本结束条件(最小规模问题的直接解决)
- 递归算法必须能改变状态向基本结束条件演进(减小问题规模)
- 递归算法必须调用自身(解决减小了规模相同的问题)
以十进制为例
def tostr(n,base):
convert_string = '0123456789ABCDEEF'
if n < base:
return convert_string[n]
else:
return tostr(n//base,base)+convert_string[n%base]
print(tostr(1453,16))
import turtle
t = turtle.Turtle()
def draw_spiral(t,linelen):
if linelen>0:
t.forward(linelen)
t.right(90)
draw_spiral(t,linelen-5)
draw_spiral(t,100)
turtle.done()
import turtle
def tree(branch_len):
if branch_len>5:
t.forward(branch_len)
t.right(20)
tree(branch_len-15)
t.left(40)
tree(branch_len-15)
t.right(20)
t.backward(branch_len)
t = turtle.Turtle()
t.left(90)
t.penup()
t.backward(100)
t.pendown()
t.pencolor('green')
t.pensize(2)
tree(75)
t.hideturtle()
turtle.done()
import turtle
def sierpinski(degree,points):
colormap = ['blue','red','green','white','yellow','orange']
drawTriangle(points,colormap[degree])
if degree>0:
sierpinski(degree-1,
{'left':points['left'],
'right':getMid(points['left'],points['right']),
'top':getMid(points['top'],points['left'])})
sierpinski(degree-1,
{'left':getMid(points['top'],points['left']),
'right':getMid(points['left'],points['right']),
'top':points['top']})
sierpinski(degree - 1,
{'left': getMid(points['top'], points['left']),
'right': points['right'],
'top': getMid(points['top'], points['right'])})
def drawTriangle(points,color):
t.fillcolor(color)
t.penup()
t.goto(points['top'])
t.pendown()
t.begin_fill()
t.goto(points['left'])
t.goto(points['right'])
t.goto(points['top'])
t.end_fill()
def getMid(p1,p2):
return ((p1[0]+p2[0])/2,(p1[1]+p2[1])/2)
t = turtle.Turtle()
points = {'left':(-200,-100),
'top':(0,200),
'right':(200,-100)}
sierpinski(5,points)
turtle.done()
代码实现:
def moveTower(height,fromPole,withPole,toPole):
if height>=1:
moveTower(height-1,fromPole,toPole,withPole)
moveDisk(fromPole,toPole)
moveTower(height-1,withPole,fromPole,toPole)
def moveDisk(disk,fromPole,toPole):
print(f"moving disk[{disk}] from {fromPole} to {toPole}")
moveTower(3,"#1","#2","#3")
代码实现:
读取迷宫文本文件
class Maze:
def __init__(self,mazeFilename):
# 读取文件
rowsInMaze = 0
colsInMaze = 0
sele.mazelist = []
mazeFile = open(mazeFilename,'r')
for line in mazeFile:
rowList = []
col = 0
for ch in line:
rowList.append(ch)
if ch == "s":
self.startRow = rowsInMaze
self.startCol = colsInMaze
col = col+1
rowsInMaze = rowsInMaze +1
self.mazelist.append(rowList)
colsInMaze = len(rowList)
探索过程
def searchFrom(maze,startRow,startCol):
# 1.碰到墙壁,返回失败
maze.updatePosition(startRow,startCol)
if maze[startRow][startCol] == OBSTACLE:
return False
# 2.碰到面包屑,或者死胡同,返回失败
if maze[startRow][startCol] ==TRIED or \
maze[startRow][startCol] == DEAD_END:
return False
# 3.碰到出口,返回成功
if maze.isExit(startRow,startCol):
maze.updatePosition(startRow,startCol,PART_OF_PATH)
return True
# 4.洒面包屑,继续探索
maze.updatePosition(startRow,startCol,TRIED)
# 向北南西东方向依次探索,or操作符具有短路效应
found = searchFrom(maze,startRow-1,startCol) or \
searchFrom(maze,startRow+1,startCol) or \
searchFrom(maze, startRow, startCol-1) or \
searchFrom(maze, startRow, startCol+1)
# 如果探索成功,标记当前点,失败标为死胡同
if found:
maze.updatePosition(startRow,startCol,PART_OF_PATH)
else:
maze.updatePosition(startRow,startCol,DEAD_END)
return found
应用:排序,查找,遍历,求值等
贪心策略(Greedy Method) :每次试图解决问题的尽量大的一部分
代码实现(存在重复计算问题,极其低效):
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))
改进:
def recDC(coinValueList,change,knownResult):
minCoins = change
if change in coinValueList:
knownResult[change] = 1
return 1
elif knownResult[change]>0:
return knownResult[change] # 使用最优解
else:
for i in [c for c in coinValueList if c<=change]:
numCoins = 1 + recDC(coinValueList,change-i,\
knownResult)
if numCoins < minCoins:
minCoins = numCoins
knownResult[change] = minCoins
return minCoins
1.从最简单情况开始到达所需找零的循环
2.其每一步都依靠以前的最优解来得到本步骤的最优解,直至找到答案
def dpMakeChange(coinValueList,change,minCoins):
# 从1分开始到change逐个计算最小硬币数
for cents in range(1,change+1):
# 1.初始化一个最大值
coinCount = cents
# 2.减去每个硬币,向后查最少硬币数,同时记录总的最小数
for j in [c for c in coinValueList if c<=cents]:
if minCoins[cents-j]+1<coinCount:
coinCount = minCoins[cents-j]+1
# 3.得到当前最少硬币数,记录到表中
minCoins[cents] = coinCount
# 返回最后一个结果
return minCoins[change]
def dpMakeChange(coinValueList,change,minCoins,coinUsed):
# 从1分开始到change逐个计算最小硬币数
for cents in range(1,change+1):
# 1.初始化一个最大值
coinCount = cents
newCoin = 1
# 2.减去每个硬币,向后查最少硬币数,同时记录总的最小数
for j in [c for c in coinValueList if c<=cents]:
if minCoins[cents-j]+1<coinCount:
coinCount = minCoins[cents-j]+1
newCoin = j
# 3.得到当前最少硬币数,记录到表中
minCoins[cents] = coinCount
# 返回最后一个结果
return minCoins[change]
def printCoins(coinsUsed,change):
coin = change
while coin >0:
thisCoin = coinsUsed[coin]
print(thisCoin)
coin -= thisCoin
动态规划代码实现
# 宝物的重量和价值
tr = [None,{"w":2,"v":3},{"w":4,"v":8},{"w":3,"v":4},
{"w":5,"v":8},{"w":9,"v":10}]
# 大盗最大承重
max_w = 20
# 初始化二维表格m[(i,w)]
# 表示当前i个宝物中,最大重量w的组合,所得到的最大价值
# 当i什么都不取,或w上限为0,价值均为0
m = {(i,w):0 for i in range(len(tr)) for w in range(max_w+1)}
# 逐个填写二维表
for i in range(1,len(tr)):
for w in range(1,max_w+1):
if tr[i]['w'] >w: # 装不下第i个宝物
m[(i,w)]=m[(i-1,w)]
else:
# 不装第i个宝物,装第i个宝物,两种情况下最大值
m[(i,w)]=max(m[i-1,w],m[(i-1,w-tr[i]['w'])]+tr[i]["v"])
递归代码实现
# 宝物的重量和价值
tr = {(2,3),(3,4),(4,8),(5,8),(9,10)}
# 大盗最大承重
max_w = 20
# 初始化二维表格m[(i,w)]
# key是(宝物组合,最大重量),value是最大价值
m = {}
def thief(tr,w):
if tr == set() or w == 0:
m[(tuple(tr),w)] = 0 # tuple 是key的要求
return 0
elif (tuple(tr),w) in m:
return m[(tuple(tr),w)]
else:
vmax = 0
for t in tr:
if t[0] <= w:
# 逐个从集合中去掉某个宝物,递归调用
# 选出所有价值中的最大值
v = thief(tr-{t},w-t[0]) + t[1]
vmax = max(vmax,v)
m[(tuple(tr),w)] = vmax
return vmax
1.递归算法“三定律”:
2.记忆化/函数值缓存:
通过记录中间计算结果来减少重复计算
3.动态规划:
若问题最优解包括规模更小相同问题的最优解,可用动态规划解决。
未完待续。。。