《羊了個羊》12月7日話題PK通關攻略
阿新 • • 發佈:2022-12-07
目錄
- 1.遞迴實現十進位制轉換2/8/16進位制
- 2.遞迴呼叫的實現
- 3.分形樹:自相似遞迴圖形
- 4.謝爾賓斯基Sierpinski三角形
- 5.漢諾塔
- 6.分治策略
- 7.優化問題
- 8.找零兌換問題:遞迴解法
- 9.找零兌換:動態規劃解法
- 10.博物館大盜問題
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()
# 第一種:畫玫瑰的方法
from turtle import *
import time
# 初始化玫瑰
# 畫布大小
#(600, 800, 0, 0)
speed(0)
penup() # 提起畫筆
seth(90) # 朝向90度
fd(340) # 向前移動指定的距離
seth(0)
pendown() # 放下畫筆
# 開始畫
speed(5) # 畫筆移動速度為5秒
begin_fill() # 開始填充
fillcolor('red') # 為紅色
circle(50, 30) # 畫一個半徑為50,弧度為30的圓
for i in range(10):
fd(1)
left(10) # 逆時針轉動畫筆10度
circle(40, 40)
for i in range(6):
fd(1)
left(3)
circle(80, 40)
for i in range(20):
fd(0.5)
left(5)
circle(80, 45)
for i in range(10):
fd(2)
left(1)
circle(80, 25)
for i in range(20):
fd(1)
left(4)
circle(50, 50)
time.sleep(0.1)
circle(120, 55)
speed(3)
seth(-90)
fd(70)
right(150) # 順時針轉動畫筆150度
fd(20)
left(140)
circle(140, 90)
left(30)
circle(160, 100)
left(130)
fd(25)
penup()
right(150)
circle(40, 80)
pendown()
left(115)
fd(60)
penup()
left(180)
fd(60)
pendown()
end_fill()
right(120)
circle(-50, 50)
circle(-20, 90)
speed(1)
fd(75)
speed(1)
circle(90, 110)
penup()
left(162)
fd(185)
left(170)
pendown()
circle(200, 10)
circle(100, 40)
circle(-52, 115)
left(20)
circle(100, 20)
circle(300, 20)
speed(1)
fd(250)
penup()
speed(2)
left(180)
fd(250)
circle(-300, 7)
right(80)
circle(200, 5)
pendown()
left(60)
begin_fill()
fillcolor('green')
circle(-80, 100)
right(90)
fd(10)
left(20)
circle(-63, 127)
end_fill()
penup()
left(50)
fd(20)
left(180)
pendown()
circle(200, 25)
penup()
right(150)
fd(180)
right(40)
pendown()
begin_fill()
fillcolor('green')
circle(-100, 80)
right(150)
fd(10)
left(60)
circle(-80, 98)
end_fill()
penup()
left(60)
fd(13)
left(180)
pendown()
speed(1)
circle(-200, 23)
hideturtle()
done()
# 第二種:
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))