5.递归
目录
1.递归实现十进制转换2/8/16进制
# 十进制转换2/8/16进制
def to_str(n,base):
convert_string = "0123456789ABCDEF"
if n < base:
return convert_string[n] # 最小规模
else: # 减小规模,调用自身
return to_str(n//base, base) + convert_string[n % base]
print(to_str(1453,16))
任意进制之间相互转换(<=35进制):
m, n = [int(x) for x in input().split()]
k = input()
chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
def nbase2int(k, n): # n代表进制,nbase2int将n进制转化为十进制整数,k是字符串
if len(k) == 1:
return chars.index(k)
else:
return nbase2int(k[:-1],n) * n + chars[k[-1]]
def int2nbase(k, n): # 十进制整数转为n进制,k是整数
if k < n:
return chars[k]
else:
return int2nbase(k // n, n) + chars[k % n]
print(int2nbase(nbase2int(k, m),n))
# 找零兑换问题:递归解法改进代码
def recDC(coin_value_list, change, known_results):
min_coins =change
if change in coin_value_list: # 递归基本结束条件
known_results[change] = 1 # 记录最优解
return 1
elif known_results[change] > 0:
return known_results[change] # 查表成功,直接用最优解
else:
for i in [c for c in coin_value_list if c<= change]:
num_coins = 1 + recDC(coin_value_list, change - i, known_results)
if num_coins < min_coins:
min_coins = num_coins
# 找到最优解,记录到表中
known_results[change] = min_coins
return min_coins
start = time.time()
print(recDC([1, 5, 10, 21, 25], 63, [0]*64))
2.递归调用的实现
当一个函数被调用的时候,系统会把调用时的现场数据压入到系统调用栈。每次调用,压入栈的现场数据成尾栈帧,当函数返回时,要从调用栈的栈顶取得返回地址,恢复现场,弹出栈帧,按地址返回。
在调试递归算法程序的时候经常会碰到这样的错误:RecursionError。这是递归的层数太多,系统调用栈容量有限。
给你讲个故事:
“从前有座山,山上有座庙,庙里有个老和尚,他在讲:
“从前有座山,山上有座庙,庙里有个老和尚,他在讲:
“从前有座山,山上有座庙,庙里有个老和尚,他在讲:……”””
这时候要检查程序中是否忘记设置基本结束条件,导致无限递归。或者向基本结束条件严谨太慢,导致递归层数太多,调用栈溢出。
def tell_story():
print("“从前有座山,山上有座庙,庙里有个老和尚,他在讲:")
tell_story()
print("给你讲个故事:")
tell_story()
在python内置的sys模块可以获取和调整最大递归深度:
import sys
sys.getrecursionlimit() # 1000或997,python默认设置的最大递归深度是1000,超过1000抛错。但可以修改:
sys.setrecursionlimit(3000)
python的海龟作图系统turtle module
python内置,随时可用,以LOGO语言的创意为基础,其一项为模拟海龟在沙滩上爬行而留下的足迹
爬行:forward(n);backward(n)
转向:left(a);right(a)
抬笔放笔:penup();pendown()
笔属性:pensize(s);pencolor(c)
import turtle
t = turtle.Turtle()
# 作图开始
t.forward(100) # 指挥海龟作图
# 作图结束
turtle.done()
import turtle,time,random
t = turtle.Turtle()
i = 0
step = random.randrange(25,50)
degree = random.randrange(1,170)
while i<100:
# 作图开始
if (-1)**i > 0:
t.left(degree)
t.pencolor("green")
t.pensize(10)
t.forward(step) # 指挥海龟作图
else:
t.right(degree)
t.pencolor("red")
t.pensize(5)
t.forward(step) # 指挥海龟作图
time.sleep(0.3)
i +=1
# 作图结束
turtle.done()
# 画正方形
import turtle
t = turtle.Turtle()
for i in range(4):
t.pencolor("red")
t.width(5)
t.forward(100)
t.right(90)
turtle.done()
# 画五角星
import turtle
t = turtle.Turtle()
t.pencolor("red")
t.pensize(3)
for i in range(5):
t.forward(300)
t.right(144)
t.hideturtle()
turtle.done()
import turtle # 画螺旋线
t = turtle.Turtle()
def draw_spiral(t, line_len):
if line_len > 0:
t.forward(line_len)
t.right(90)
draw_spiral(t,line_len-5)
draw_spiral(t, 100)
turtle.done()
Python turtle 库基本颜色
因为经常查看,所以就保存到自己的博客里好了
# 第一种:画玫瑰的方法
# coding:utf-8
import turtle,time
class DrawFlower:
def __init__(self,rose_color='red',font_color='purple',font_type='宋徽宗瘦金体', flag=True):
self.initial_canvas()
self.pen_initiation_point()
self.draw_flower(rose_color)
self.draw_stem()
self.draw_right_leaf()
self.draw_left_leaf()
self.add_word(font_color, font_type)
# 设置画布大小(800x800)
def initial_canvas(self):
screen = turtle.Screen()
screen.setup(800, 800)
# time.sleep(5)
# 画笔移动到起始点
def pen_initiation_point(self):
turtle.speed(1)
turtle.penup() # 提起画笔
turtle.seth(90) # 朝向90度
turtle.fd(340) # 向前移动指定的距离
turtle.seth(0)
turtle.pendown() # 放下画笔
# 画花朵&填充颜色
def draw_flower(self, rose_color):
turtle.speed(5) # 画笔移动速度为5秒
turtle.begin_fill() # 开始填充
turtle.fillcolor(rose_color) # 为红色
turtle.circle(50, 30) # 画一个半径为50,弧度为30的圆
for i in range(10):
turtle.fd(1)
turtle.left(10) # 逆时针转动画笔10度
turtle.circle(40, 40)
# time.sleep(5)
for i in range(6):
turtle.fd(1)
turtle.left(3)
turtle.circle(80, 40)
for i in range(20):
turtle.fd(0.5)
turtle.left(5)
turtle.circle(80, 45)
for i in range(10):
turtle.fd(2)
turtle.left(1)
turtle.circle(80, 25)
for i in range(20):
turtle.fd(1)
turtle.left(4)
turtle.circle(50, 50)
time.sleep(0.1)
turtle.circle(120, 55)
turtle.speed(3)
turtle.seth(-90)
turtle.fd(70)
turtle.right(150) # 顺时针转动画笔150度
turtle.fd(20)
turtle.left(140)
turtle.circle(140, 90)
turtle.left(30)
turtle.circle(160, 100)
turtle.left(130)
turtle.fd(25)
turtle.penup()
turtle.right(150)
turtle.circle(40, 80)
turtle.pendown()
turtle.left(115)
turtle.fd(60)
turtle.penup()
turtle.left(180)
turtle.fd(60)
turtle.pendown()
turtle.end_fill()
turtle.right(120)
turtle.circle(-50, 50)
turtle.circle(-20, 90)
turtle.speed(1)
turtle.fd(75)
turtle.speed(1)
turtle.circle(90, 110)
turtle.penup()
turtle.left(162)
turtle.fd(185)
turtle.left(170)
turtle.pendown()
turtle.circle(200, 10)
turtle.circle(100, 40)
turtle.circle(-52, 115)
turtle.left(20)
# 调线条粗细&线条颜色&画茎
def draw_stem(self):
turtle.pencolor('dimgray')
turtle.pensize(3)
turtle.circle(100, 20)
turtle.circle(300, 20)
turtle.speed(1)
turtle.fd(380)
turtle.penup()
turtle.speed(2)
turtle.left(180)
# fd(250)
# 画右上叶子&填充颜色
def draw_right_leaf(self):
# 移动箭头到画右上叶子的位置
turtle.fd(360)
turtle.pencolor('black')
turtle.pensize(1)
turtle.circle(-300, 7)
turtle.right(80)
turtle.circle(200, 5)
turtle.pendown()
turtle.left(60)
turtle.begin_fill()
turtle.fillcolor('green')
turtle.circle(-80, 100)
turtle.right(90)
turtle.fd(10)
turtle.left(20)
turtle.circle(-63, 127)
turtle.end_fill()
turtle.penup()
turtle.left(50)
turtle.fd(20)
turtle.left(180)
turtle.pendown()
turtle.circle(200, 25)
turtle.penup()
turtle.right(150)
turtle.fd(180)
# 画左下叶子&填充颜色
def draw_left_leaf(self):
turtle.right(40)
turtle.pendown()
turtle.begin_fill()
turtle.fillcolor('green')
turtle.pencolor('black')
turtle.circle(-100, 80)
turtle.right(150)
turtle.fd(10)
turtle.left(60)
turtle.circle(-80, 98)
turtle.end_fill()
turtle.penup()
turtle.left(60)
turtle.fd(13)
turtle.left(180)
turtle.pendown()
turtle.speed(1)
turtle.circle(-200, 23)
# 隐藏箭头
turtle.hideturtle()
# 添加文字信息之前,将画笔设置为指定颜色
def add_word(self, font_color, font_type):
turtle.penup()
# 以画布中心为原点(0,0),arg1为横坐标x,arg2为纵坐标y
turtle.goto(-330, 0)
turtle.color(font_color)
# turtle.write("xxx", align='center', font=("宋徽宗瘦金体",18 , "bold italic"))
turtle.write("x", font=(font_type, 18, "bold"))
turtle.goto(-330, -30)
time.sleep(0.2)
turtle.write("x", font=(font_type, 18, "bold"))
turtle.goto(-330, -60)
time.sleep(0.2)
turtle.write("x", font=(font_type, 18, "bold"))
turtle.goto(-330, -90)
time.sleep(0.2)
turtle.write("x", font=(font_type, 18, "bold"))
turtle.goto(-330, -120)
time.sleep(0.2)
turtle.write("x", font=(font_type, 18, "bold"))
turtle.goto(-323, -140)
time.sleep(0.2)
turtle.write("x", font=(font_type, 18, "bold"))
turtle.goto(-330, -180)
time.sleep(0.2)
turtle.write("x", font=(font_type, 18, "bold"))
turtle.goto(-330, -210)
time.sleep(0.2)
turtle.write("x", font=(font_type, 18, "bold"))
turtle.goto(-330, -240)
time.sleep(0.2)
turtle.write("x", font=(font_type, 18, "bold"))
turtle.goto(-330, -270)
time.sleep(0.2)
turtle.write("x", font=(font_type, 18, "bold"))
turtle.goto(-330, -300)
time.sleep(0.2)
turtle.write("x", font=(font_type, 18, "bold"))
turtle.goto(-325, -330)
time.sleep(0.2)
turtle.write("x", font=(font_type, 18, "bold"))
turtle.goto(300,-390)
turtle.write("日期", font=(font_type, 10, "bold"))
turtle.pendown()
turtle.end_fill()
# turtle.hideturtle()
turtle.done()
if __name__ == '__main__':
rose_color = 'mediumblue' # red pink purple gold mediumblue blue navy moccasin
font_color = 'purple'
font_type = '华文行楷' # 宋体、隶书、楷体、微软雅黑、华文行楷、宋徽宗瘦金体
flag = True
DrawFlower(rose_color, font_color, font_type, flag)
# 第二种:
import turtle
def initialization():
# turtle.setup(width=0.9, height=0.9)
turtle.speed(10)
def flower():
turtle.goto(0, 200)
turtle.fillcolor("red")
turtle.begin_fill()
turtle.circle(10, 180)
turtle.circle(25, 110)
turtle.left(50)
turtle.circle(60, 45)
turtle.circle(20, 170)
turtle.right(24)
turtle.fd(30)
turtle.left(10)
turtle.circle(30, 110)
turtle.fd(20)
turtle.left(40)
turtle.circle(90, 70)
turtle.circle(30, 150)
turtle.right(30)
turtle.fd(15)
turtle.circle(80, 90)
turtle.left(15)
turtle.fd(45)
turtle.right(165)
turtle.fd(20)
turtle.left(155)
turtle.circle(150, 80)
turtle.left(50)
turtle.circle(150, 90)
turtle.end_fill()
def peta1():
turtle.left(150)
turtle.circle(-90, 70)
turtle.left(20)
turtle.circle(75, 105)
turtle.setheading(60)
turtle.circle(80, 98)
turtle.circle(-90, 40)
def peta2():
turtle.left(180)
turtle.circle(90, 40)
turtle.circle(-80, 98)
turtle.setheading(-83)
def leaf1():
turtle.fd(30)
turtle.left(90)
turtle.fd(25)
turtle.left(45)
turtle.fillcolor("green")
turtle.begin_fill()
turtle.circle(-80, 90)
turtle.right(90)
turtle.circle(-80, 90)
turtle.end_fill()
turtle.right(135)
turtle.fd(60)
turtle.left(180)
turtle.fd(85)
turtle.left(90)
turtle.fd(80)
def leaf2():
turtle.right(90)
turtle.right(45)
turtle.fillcolor("green")
turtle.begin_fill()
turtle.circle(80, 90)
turtle.left(90)
turtle.circle(80, 90)
turtle.end_fill()
turtle.left(135)
turtle.fd(60)
turtle.left(180)
turtle.fd(60)
turtle.right(90)
turtle.circle(200, 60)
if __name__ == '__main__':
initialization()
flower()
peta1()
peta2()
leaf1()
leaf2()
3.分形树:自相似递归图形
自然界中能找到众多具有分形性质的物质:海岸线、山脉、闪电、云朵、雪花、树
自然现象中所具备的分形特性,使得计算机可以通过分形算法生成非常逼真的自然场景。
分形是在不同尺度上都具有相似性的事物,我们能看出一棵树的每隔分叉和每条树枝,实际上都具有整棵树的外形特征(也是逐步分叉的)。这样我们可以把树分解为三部分:树干、左边的小树、右边的小树。分解后,正好符合递归的定义:对自身的调用
import turtle
def tree(branch_len):
if branch_len > 5: # 树干太短不画,即递归结束条件
t.forward(branch_len) # 画树干
t.right(20) # 右倾斜20度
tree(branch_len-15) # 递归调用,画右边的小数,树干减15
t.left(40) # 向左回40度,即左倾斜20度
tree(branch_len - 15) # 递归调用,画左边的小树,树干减15
t.right(20) # 向右回20度,即回正
t.backward(branch_len) # 海龟退回原位置
t = turtle.Turtle()
t.left(90)
t.penup()
# t.backward(100)
t.backward(225)
t.pendown()
t.pencolor('green')
t.pensize(2)
# tree(75) # 画树干长度75的二叉树
tree(210) # 画树干长度75的二叉树
t.hideturtle()
turtle.done()
4.谢尔宾斯基Sierpinski三角形
分形构造,平面称谢尔宾斯基三角形,立体称谢尔宾斯基金字塔。实际上,真正的谢尔宾斯基三角形是完全不可见的,其面积为0,但周长无穷,是介于一维和二维之间的分数维(约1.5.85)构造。
根据自相似特性,谢尔宾斯基三角形是由3个尺寸减半的谢尔宾斯基三角形按照品字形拼叠而成。由于我们无法真正做出谢尔宾斯基三角形(degree->∞),只能做degree有限的近似图形。
#作图思路
在degree有限的情况下,degree=n的三角形,是由3个degree=n-1的三角形按照品字形拼叠而成。同时,这3个degree=n-1的三角形边长均为degree=n的三角形的一半(规模减小)。
当degree=0,则就是一个等边三角形,这是递归基本结束条件
import turtle as t
t.pensize(2)
t.speed(10)
def get_midpoint(a, b): # 取两个点的中点
ax, ay = a
bx, by = b
return (ax + bx) / 2, (ay + by) / 2
def draw_triangle(a, b, c, color): # 绘制等边三角形
ax, ay = a
bx, by = b
cx, cy = c
t.fillcolor(color)
t.penup()
t.goto(ax, ay)
t.pendown()
t.begin_fill()
t.goto(bx, by)
t.goto(cx, cy)
t.goto(ax, ay)
t.end_fill()
def draw_sierpinski(triangle, depth):
"""
:param trangle:指定三角形三个顶点坐标,示例:((ax,ay),(bx,by),(cx,cy))
:param depth:指定层数
"""
colormap = ['blue', 'red', 'green', 'white','yellow', 'orange' ]
a, b, c = triangle
draw_triangle(a, b, c, colormap[depth])
if depth == 0:
return
else:
d = get_midpoint(a, b)
e = get_midpoint(b, c)
f = get_midpoint(c, a)
draw_sierpinski([a, d, f], depth - 1)
draw_sierpinski([d, b, e], depth - 1)
draw_sierpinski([f, e, c], depth - 1)
triangle = [[-200, -100], [0, 200], [200, -100]] # 外轮廓三个顶点
draw_sierpinski(triangle, 5) # 画degree=5的三角形
t.done()
ASCII谢尔宾斯基地毯:
n = int(input()) # 阶数
ch = input() # 构成字符
blank = " " * len(ch) # 构成空白
pic = [[ch for col in range(n)] for row in range(n)] # 画板
def spski(n, top, left): # n阶, 左上角的行列数
if n == 1: # 基本结束条件
return
# 分为3行3列,挖掉中心,其余递归n//3
for row in range(3):
for col in range(3):
if row ==1 and col == 1: # 挖空
for r1 in range(n // 3):
for c1 in range(n // 3):
pic[top + n // 3 + r1][left + n // 3 + c1] = blank
else: # 递归n//3
spski(n //3, top + row * n // 3, left + col * n // 3)
spski(n, 0, 0) # 挖n阶
for r in range(n):
print("".join(pic[r]))
import turtle
# t = turtle.Turtle()
turtle.tracer(0)
t = turtle.Pen()
nmax, width = 3 ** 5, 600 # 最大阶数,地毯尺寸
cell = width / nmax # 最小格子尺寸
def box(top, left,size, c): # 按照行列画一个指定颜色格子
t.penup()
t.goto(left * cell - width / 2, width / 2 - top * cell)
t.pendown()
t.color(c)
t.begin_fill()
for n in range(4):
t.forward(size * cell)
t.right(90)
t.end_fill()
def spskl(n, top, left):
if n == 1:
return
for row in range(3):
for col in range(3):
if row ==1 and col == 1: # 挖空
box(top * n // 3, left * n // 3, n // 3, "red")
else:
spskl(n // 3, top + row * n // 3, left + col * n // 3)
box(0, 0, nmax, "navy")
spskl(nmax, 0, 0) # 挖n阶
t.hideturtle()
turtle.update()
turtle.done()
5.汉诺塔
'''
将盘片塔从开始柱,经由中间柱,移动到目标柱:
1.首先将上层N-1个盘片的盘片塔,从开始柱,经由目标柱,移动到中间柱;
2.然后将第N个(最大的)盘片,从开始柱,移动到目标柱;
3.最后将放置在中间柱的N-1个盘片的盘片塔,经由开始柱,移动到目标柱。
基本结束条件,也就是最小规模问题是:1个盘片的移动问题
'''
def move_tower(height, from_pole, with_pole, to_pole):
if height >= 1:
move_tower(height - 1, from_pole, to_pole, with_pole)
move_disk(height, from_pole, to_pole)
move_tower(height - 1, with_pole, from_pole, to_pole)
def move_disk(disk, from_pole, to_pole):
print(f"Moving disk[{disk}] from {from_pole} to {to_pole}")
move_tower(3, "#1", "#2", "#3")
'''
Moving disk[1] from #1 to #3
Moving disk[2] from #1 to #2
Moving disk[1] from #3 to #2
Moving disk[3] from #1 to #3
Moving disk[1] from #2 to #1
Moving disk[2] from #2 to #3
Moving disk[1] from #1 to #3
'''
6.分治策略
解决问题的典型策略:分而治之
将问题分为若干更小规模的部分,通过解决每隔小规模部分问题,并将结果汇总得到原问题的解
# 递归算法与分治策略
# 递归三定律:
基本结束条件,解决最小规模问题
缩小规模,向基本结束条件演进
调用自身来解决已缩小规模的相同问题
# 体现了分治策略
问题解决依赖于若干缩小了规模的问题
汇总得到原问题的解
# 应用相当广泛
排序、查找、遍历、求值等等
7.优化问题
计算机科学中许多算法都是为了找到某些问题的最优解
例如,两个点之间的最短路径;
能最好匹配一系列点的直线;
或者满足一定条件的最小集合
# 找零兑换问题
一个经典案例是兑换最少个数的硬币问题
假设你为一家自动售货机厂家编程序,自动收货机要每次找给顾客最少数量硬币;
假设某次顾客投进$1纸币,买了¢37的东西,要找¢63,那么最少数量就是:2个quarter(¢25)、1个dime(¢10)和3个penny(¢1),一共6个。
人们会采用各种策略来解决这些问题,例如最直观的“贪心策略”
一般我们这么做,从最大面值的硬币开始,用尽量多的数量有余额的,再到下一最大面值的硬币,还用尽量多的数量,一直到penny(¢1)为止
# 贪心策略
因为我们每次都视图解决问题的尽量大的一部分对应到兑换硬币问题,就是每次以最多数量的最大面值硬币来迅速减少找零面值。
“贪心策略”解决诏令兑换问题,再美元或其它货币的硬币体系下表现尚好
# 贪心策略失效
但如果你的老板决定把自动售货机出口到Elbonia,事情就会有点复杂(系列漫画Dilbert里都钻的国家),因为这个古怪的国家除了上面3中面值之外,还有一种【¢21】的硬币!
按照“贪心策略”,在Elbonia,¢63还是原来的6个硬币:
¢63 = ¢25*2 + ¢10*1 +¢1*3
但实际上最优解是3个面值¢21的硬币!
“贪心策略失效了”
8.找零兑换问题:递归解法
我们来找一种肯定能找到最优解的方法,贪心策略是否有效依赖于具体的硬币体系。
首先是确定基本结束条件,兑换硬币这个问题最简单直接的情况就是,需要兑换的找零,其面值正好等于某种硬币。如找零25分,答案就是1个硬币!
其次是减小问题的规模,我们要对每种硬币尝试1次,例如美元硬币体系:
找零减去1分(penny)后,求兑换硬币最少数量(递归调用自身);
找零减去5分(nikel)后,求兑换硬币最少数量
找零减去10分(dime)后,求兑换硬币最少数量
找零减去25分(quarter)后,求兑换硬币最少数量
上述4项中选择最小的一个。
def rec_mc(coin_value_list, change):
min_coins = change
if change in coin_value_list: # 最小规模,直接返回
return 1
else:
for i in [c for c in coin_value_list if c <= change]:
num_coins = 1 + rec_mc(coin_value_list, change - i) # 调用自身:减小规模,每次减去一种硬币面值,挑选最小数量
if num_coins < min_coins:
min_coins = num_coins
return min_coins
import time
start = time.time()
print(rec_mc([1, 5, 10, 25], 63))
print(time.time()-start)
'''
递归解法虽然能解决问题,但其最大的问题是:及其低效。
对63分的兑换硬币问题,需要进行67,716,925次递归调用!
在我这台笔记本电脑上花费了54.585131883621216秒时间得到解:6个硬币
'''
找零兑换问题:递归解法分析
以26分兑换硬币为例,看看递归调用过程(377次递归的一小部分)。我们发现一个重大秘密,就是重复计算太多!例如找零15分的,出现了3次!而它最终解决嗨要52次递归调用。很明显,这算法致命缺点是重复计算
# 找零兑换问题:递归解法改进
对这个递归解法进行改进的关键就在于消除重复计算。我们可以用一个表将计算的中间结果保存起来,在计算之间查表看看是否已经计算过。这个算法的中间结果就是部分找零的最优解,在递归调用过程中已经得到的最优解被记录下来。在递归调用之前,先查表中是否已有部分找零的最优解。如果有,直接返回最优解而不进行递归调用;如果没有,才进行递归调用。
def recDC(coin_value_list, change, known_results):
min_coins =change
if change in coin_value_list: # 递归基本结束条件
known_results[change] = 1 # 记录最优解
return 1
elif known_results[change] > 0:
return known_results[change] # 查表成功,直接用最优解
else:
for i in [c for c in coin_value_list if c<= change]:
num_coins = 1 + recDC(coin_value_list, change - i, known_results)
if num_coins < min_coins:
min_coins = num_coins
# 找到最优解,记录到表中
known_results[change] = min_coins
return min_coins
print(recDC([1, 5, 10, 21, 25], 63, [0]*64)) # 列表长度实际根据找零兑换数值来决定
def answer_coin_num(coin_value_list, change,n = 0):
# d1 = {}
l1 = []
if change in coin_value_list:
return n + 1
for i in range(len(coin_value_list)):
if change > coin_value_list[i]:
# d1[i] = change - coin_value_list[i]
l1.append(change-coin_value_list[i])
change = min(l1)
n += 1
return answer_coin_num(coin_value_list, change, n)
print(answer_coin_num([1, 5, 10, 25], 63))
9.找零兑换:动态规划解法
中间结果记录可以很好解决找零兑换问题。实际上,这种方法还不能称为动态规划,而是叫做“memoization(记忆化/函数值缓存)”的技术提高了递归法的性能。
动态规划采用了一种更有条理的方式来得到问题的解。找零兑换的动态规划算法从最简单的“1分钱找零”的最优解开始,逐步递加上去,直到我们需要的找零钱数。在找零递加过程中,设法保持每一分钱的递加都是最优解,一直加到求解找零钱数,自然得到最优解。
递加的过程能保持最优解的关键是,其依赖于更少钱数最优解的简单计算,而更少钱数的最优解已经得到了。问题的最优解包含了更小规模子问题的最优解,这是一个最优化问题能够用动态规划策略解决的必要条件。originalamount找零兑换问题具体来说就是:
采用动态规划来解决11分钱的兑换问题。从1分钱兑换开始,逐步建立一个兑换表。
计算11分钱的兑换法,我们做如下几步:
首先减去1分硬币,剩下10分钱查表最优解是1;
然后减去5分硬币,剩下6分钱查表最优解是2;
最后减去10分硬币,剩下1分钱查表最优解是1;
通过上述最小值得到最优解:2个硬币
def dp_make_change(coin_value_list, change, min_coins):
# 从1分开始到change逐个计算最少硬币数
for cents in range(1, change + 1):
# 1.初始化一个最大值
coin_count = cents
# 2.减去每个硬币,向后查找最少硬币数,同时记录总的最少数
for j in [c for c in coin_value_list if c <= cents]:
if min_coins[cents - j] +1 < coin_count:
coin_count = min_coins[cents - j] + 1
# 3.得到当前最少硬币数,记录到表中
min_coins[cents] = coin_count
# 返回最后一个结果
return min_coins[change]
print(dp_make_change([1, 5, 10, 21, 25], 63, [0]*64))
# 动态规划求最少硬币数和硬币数各硬币对应的面值
def dp_make_change(coin_value_list, change, min_coins, coins_used):
# 从1分开始到change逐个计算最少硬币数
for cents in range(1, change + 1):
# 1.初始化一个最大值
coin_count = cents
new_coin = 1 # 初始化一下新加硬币
# 2.减去每个硬币,向后查找最少硬币数,同时记录总的最少数
for j in [c for c in coin_value_list if c <= cents]:
if min_coins[cents - j] +1 < coin_count:
coin_count = min_coins[cents - j] + 1
new_coin = j # 对应最小数量,所减的硬币
# 3.得到当前最少硬币数,记录到表中
min_coins[cents] = coin_count
coins_used[cents] = new_coin # 记录本步骤加1的一个硬币
# 返回最后一个结果
return min_coins[change]
def print_coins(coins_used,change):
coin = change
while coin > 0:
this_coin = coins_used[coin]
print(this_coin)
coin = coin-this_coin
amnt = 63
clist = [1, 5, 10, 21, 25]
coin_used = [0] * (amnt + 1)
coin_count = [0] * (amnt + 1)
print("Making change for", amnt, 'requires')
print(dp_make_change(clist, amnt, coin_count, coin_used), "coins")
print("They are:")
print_coins(coin_used, amnt)
print("The used list is as follows:")
print(coin_used)
10.博物馆大盗问题
大盗嵌入博物馆,前面有5件宝物,分别有重量和价值,大盗的背包仅能负重20公斤,请问如何选择宝物,总价值最高?
item | weight | value |
---|---|---|
1 | 2 | 3 |
2 | 3 | 4 |
3 | 4 | 8 |
4 | 5 | 8 |
5 | 9 | 10 |
我们把m(i,W)记为:前i(1<=i<=5)个宝物中,组合不超过W(1<=W<=20)重量,得到的最大价值m(i,W)应该是m(i-1,W)和m(i-1,W-Wi)+vi两者最大值,我们从m(1,1)开始计算到m(5,20)
# 宝物的重量和价值
tr = [None, {'w': 2, 'v': 3}, {'w': 3, 'v': 4}, {'w': 4, 'v': 8}, {'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)): # 第1~5件宝物
for w in range(1, max_w + 1): # 重量每增加1公斤,判断所能拿得最多宝物的重量和价值。
if tr[i]['w'] > w: # 装不下第i个宝物
m[(i, w)] = m[(i - 1, w)] # 不装第i个宝物
else:
# 不装第i个宝物,装第i个宝物,两种情况下最大价值
m[(i, w)] = max(m[(i - 1, w)], m[(i - 1, w - tr[i]['w'])] + tr[i]['v'])
# 输出结果
print(m[(len(tr) - 1, max_w)])
# 使用递归的方式解决博物馆大盗的问题
# 宝物的重量和价值
tr = {(2, 3), (3, 4), (4, 8), (5, 8), (9, 10)}
# 大盗最大承重
max_w = 20
# 初始化记忆化表格m,key是(宝物组合,最大重量),value是最大价值
m = {}
def thief(tr, w):
if tr == set() or w == 0:
m[(tuple(tr), w)] = 0
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
# 输出结果
print(thief(tr, max_w))