赞
踩
往期系列:
【动态规划】MATLAB和Python实现-Part01
前面三篇文章,我们从递归开始,了解了动态规划,并从实际例子中体会动态规划的过程。
本篇文章我们继续以实际例子体会动态规划。
我们再回想一下动态规划的基本思路:
- 定义原问题和子问题
- 定义状态
- 寻找状态转移方程
- 编程求解
有 10 件货物要从甲地运送到乙地,每件货物的重量和利润如下表所示:
物品 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|
重量 | 6 | 3 | 4 | 5 | 1 | 2 | 3 | 5 | 4 | 2 |
利润 | 540 | 200 | 180 | 350 | 60 | 150 | 280 | 450 | 320 | 120 |
由于只有一辆最大载重为 30 的火车能用来运送货物,所以只能选择部分货物进行配送,要求确定运送哪些货物,使得运送这些货物的总利润最大。
定义原问题和子问题:
原问题:
假设有
m
m
m 件物品,其中第
k
k
k 件物品的利润为
v
k
v_{k}
vk,重量为
w
k
w_{k}
wk,背包能容纳的总重量为
W
W
W,在满足重量约束的条件下,将这
m
m
m 件物品选择性地放入容量为
W
W
W 的背包,求解出所能获得的最大利润。
(假设这里的重量和利润都为正整数。)
子问题:
在满足重量约束的条件下,将前
i
(
i
≤
m
)
i(i\leq m)
i(i≤m) 件物品选择性地放入容量为
j
(
j
≤
W
)
j(j\leq W)
j(j≤W) 的背包中所能获得的最大利润。
定义状态:
记前
i
i
i 件物品选择性地放入容量为
j
j
j 的背包中所能获得的最大利润为
f
(
i
,
j
)
f(i,j)
f(i,j),那么
f
(
m
,
W
)
f(m,W)
f(m,W) 就是我们要求解的原问题的答案。
这里的
i
i
i 和
j
j
j 构成的组合就是对应子问题的状态。
寻找状态转移方程:
对于 二维 情况,我们可以先考虑问题的 边界条件:
接下来考虑问题的一般情况,即当
i
,
j
>
1
i,j>1
i,j>1 时:
假设现在背包容量为
j
j
j,前面
i
−
1
i-1
i−1 件物品已经规划好了方案,现在要考虑是否装第
i
i
i 件物品,该问题可以分为两种情况考虑(此时我们先不考虑前
i
−
1
i-1
i−1 件物品,假设背包为空):
综上所述,最终的状态转移方程为:
f
(
i
,
j
)
=
{
0
i
=
1
,
j
<
w
1
v
1
i
=
1
,
j
≥
w
1
0
w
i
>
1
,
j
=
1
v
i
w
i
=
1
,
j
=
1
f
(
i
−
1
,
j
)
w
i
>
j
,
i
>
1
,
j
>
1
m
a
x
{
v
i
+
f
(
i
−
1
,
j
−
w
i
)
,
f
(
i
−
1
,
j
)
}
w
i
≤
j
,
i
>
1
,
j
>
1
f(i,j)=
在进行完题目分析后,我们用 MATLAB
和 Python
对题目进行编程求解:
MATLAB
函数实现:
function f = 01pack(v,w,W) m = length(v); dp = zeros(m,W); if w(1)<=W % 初始化dp第一行 dp(1,w(1):end) = v(1); end for i = 2:m % 初始化dp第一列 dp(i,1) = max([0,v(w(1:i)==1)]); end % i,j>1 for i = 2:m for j= 2:W if w(i)>j dp(i,j) = dp(i-1,j); elseif w(i)==j dp(i,j) = max(dp(i-1,j),v(i)); else dp(i,j) = max(v(i)+dp(i-1,j-w(i)),dp(i-1,j)) end end end f = dp(m,W); end
函数调用:
clear;clc;
v = [540, 200, 180, 350, 60, 150, 280, 450, 320, 120];
w = [6, 3, 4, 5, 1, 2, 3, 5, 4, 2];
W = 30;
tic;
f = dp01pack(v,w,W)
toc;
结果:
Python
函数实现:
def dp01pack(v, w, W): m = len(v) # 物品个数 # 初始化dp数组 m*M 元素全为0 dp = [[0 for j in range(W)] for i in range(m)] # 处理第一行 if w[0]<W: # 如果第一个物品重量小于W for j in range(w[0]-1,W): # 那么第一行从 j>=w[0]-1 之后,最大价值都为v[0] dp[0][j] = v[0] # 处理第一列 for i in range(1, m): temp = [0] for k in range(i+1): if w[k]==1: temp.append(v[k]) dp[i][0] = max(temp) # 处理剩下的部分 for i in range(1, m): for j in range(1, W): if w[i]>(j+1): dp[i][j] = dp[i-1][j] elif w[i]==(j+1): dp[i][j] = max(v[i], dp[i-1][j]) else: dp[i][j] = max(v[i]+dp[i-1][j-w[i]], dp[i-1][j]) f = dp[-1][-1] return f
函数调用:
import time
v = [540, 200, 180, 350, 60, 150, 280, 450, 320, 120]
w = [6, 3, 4, 5, 1, 2, 3, 5, 4, 2]
W = 30
start = time.time()
print('01背包问题的最大价值为:')
f = dp01pack(v, w, W)
print(f)
end = time.time()
print('算法用时为(s):')
print('%.8f' % (end-start))
结果:
在通过前面的步骤获得物品的最大价值后,我们来进一步思考:
如何得到选择物品的编号?
仍然是从 DP数组入手!
p
存入数组 IND
中)IND
(可以逆序输出),若不是,则重复前一步骤。接下来我们分别用 MATLAB
和 Python
实现此需求:
MATLAB
函数实现:
function [f, IND] = dp01pack_ind(v,w,W) m = length(v); dp = zeros(m,W); if w(1)<=W % 初始化dp第一行 dp(1,w(1):end) = v(1); end for i = 2:m % 初始化dp第一列 dp(i,1) = max([0,v(w(1:i)==1)]); end % i,j>1 for i = 2:m for j= 2:W if w(i)>j dp(i,j) = dp(i-1,j); elseif w(i)==j dp(i,j) = max(v(i), dp(i-1,j)); else dp(i,j) = max(v(i)+dp(i-1,j-w(i)),dp(i-1,j)); end end end f = dp(m,W); IND = []; if f>0 temp = dp(:,W); while 1 ind = find(temp==max(temp),1); W = W-w(ind); IND = [IND,ind]; if ind>1 && W>0 temp = dp(1:ind-1,W); else break end end IND = sort(IND); end end
函数调用:
clear;clc;
v = [540, 200, 180, 350, 60, 150, 280, 450, 320, 120];
w = [6, 3, 4, 5, 1, 2, 3, 5, 4, 2];
W = 30;
% v = [11, 12, 10, 26, 14, 16];
% w = [3, 2, 2, 5, 1, 3];
% W = 10;
tic;
[f, IND] = dp01pack_ind(v,w,W)
toc;
结果:
Python
函数实现:
def dp01pack_ind(v, w, W): m = len(v) # 物品个数 # 初始化dp数组 m*M 元素全为0 dp = [[0 for j in range(W)] for i in range(m)] # 处理第一行 if w[0]<W: # 如果第一个物品重量小于W for j in range(w[0]-1,W): # 那么第一行从 j>=w[0]-1 之后,最大价值都为v[0] dp[0][j] = v[0] # 处理第一列 for i in range(1, m): temp = [0] for k in range(i+1): if w[k]==1: temp.append(v[k]) dp[i][0] = max(temp) # 处理剩下的部分 for i in range(1, m): for j in range(1, W): if w[i]>(j+1): dp[i][j] = dp[i-1][j] elif w[i]==(j+1): dp[i][j] = max(v[i], dp[i-1][j]) else: dp[i][j] = max(v[i]+dp[i-1][j-w[i]], dp[i-1][j]) f = dp[-1][-1] # 输出编号 IND = [] if f>0: temp = [] for i in dp: # 取出dp最后一列 temp.append(i[W-1]) while 1: ind = temp.index(max(temp)) W = W-w[ind] IND.append(ind+1) if ind>0 and W>0: temp = [] for i in dp[0:ind]: temp.append(i[W-1]) else: break IND.sort() return f, IND
函数调用:
import time
v = [540, 200, 180, 350, 60, 150, 280, 450, 320, 120]
w = [6, 3, 4, 5, 1, 2, 3, 5, 4, 2]
W = 30
start = time.time()
[f, IND] = dp01pack_ind(v, w, W)
print('01背包问题的最大价值为:')
print(f)
print('选择物品的编号为:')
print(IND)
end = time.time()
print('算法用时为(s):')
print('%.8f' % (end-start))
结果:
给定不同面值的
m
m
m 种硬币 coins
和一个总金额
S
S
S,请编写一个函数来计算用这些硬币可以凑成总金额
S
S
S 的方案数。(每种硬币数量是无限的,
S
S
S 以及 coins
中的元素都是正整数,且不考虑每种方案中硬币的顺序)
示例输入:
S=4, coins=[1, 2, 3]
示例输出:
4
解释:
有4种方案:[1, 1, 1, 1]
,[1, 1, 2]
,[2, 2]
和 [1, 3]
定义原问题和子问题:
原问题:
能够使用所有面值的硬币来凑出总金额 S
的方案数。
子问题:
只能使用前
i
(
i
≤
m
)
i(i\leq m)
i(i≤m) 种面值的硬币来凑出总金额
j
(
j
≤
S
)
j(j\leq S)
j(j≤S) 的方案数。
定义状态:
根据子问题的定义,我们可以看出每种状态包含两个参数:第一个参数就是我们可以使用前多少种面值的硬币;第二个参数就是要凑出的总金额数。
因此,我们记
f
(
i
,
j
)
f(i,j)
f(i,j) 为只能使用前
i
(
i
≤
m
)
i(i\leq m)
i(i≤m) 种面值的硬币来凑出总金额
j
(
j
≤
S
)
j(j\leq S)
j(j≤S) 的方案数,当
i
=
m
i=m
i=m 且
j
=
S
j=S
j=S 时就是原问题的解。
寻找状态转移方程:
对于二维动态数组,我们还是先考虑边界条件:
接下来考虑一般情况,当 i > 1 , j > 1 i>1,j>1 i>1,j>1 时:
则得到一般情况下的状态转移方程为:
f
(
i
,
j
)
=
{
f
(
i
−
1
,
j
)
j
−
c
o
i
n
s
i
<
0
f
(
i
−
1
,
j
)
+
1
j
−
c
o
i
n
s
i
=
0
f
(
i
−
1
,
j
)
+
f
(
i
,
j
−
c
o
i
n
s
i
)
j
−
c
o
i
n
s
i
>
0
f(i,j)=
MATLAB
函数实现:
function [f,dp] = dp_coin(coins,S) coins = sort(coins); m = length(coins); dp = zeros(m,S); dp(1,:) = (mod(1:S,coins(1))==0); dp(:,1) = (coins(1)==1); for i = 2:m for j = 2:S if j-coins(i)<0 dp(i,j) = dp(i-1,j); elseif j-coins(i)==0 dp(i,j) = dp(i-1,j)+1; else dp(i,j) = dp(i-1,j)+dp(i,j-coins(i)); end end end f = dp(m,S); end
函数调用:
clear;clc;
coins = [2, 3, 5, 6];
S = 10;
tic;
[f,dp] = dp_coin(coins,S)
toc;
结果:
Python
函数实现:
def dp_coin(coins, S): coins.sort() m = len(coins) dp = [[0 for j in range(S)] for i in range(m)] for j in range(S): if (j+1)%coins[0]==0: dp[0][j] = 1 for i in range(m): if coins[0]==1: dp[i][0] = 1 for i in range(1, m): for j in range(1, S): if j+1-coins[i]<0: dp[i][j] = dp[i-1][j] elif j+1-coins[i]==0: dp[i][j] = dp[i-1][j]+1 else: dp[i][j] = dp[i-1][j]+dp[i][j-coins[i]] f = dp[-1][-1] return f, dp
函数调用:
import time
coins = [2, 3, 5, 6]
S = 10
start = time.time()
[f,dp] = dp_coin(coins,S)
print('最终方案数为:')
print(f)
print('DP数组为:')
for i in dp:
print(i)
end = time.time()
print('算法用时为(s):')
print('%.8f' % (end-start))
结果:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。