一、实验目的
1.加深学生对动态规划算法设计方法的基本思想、基本步骤、基本方法的理解与掌握;
2.提高学生利用课堂所学知识解决实际问题的能力;
3.提高学生综合应用所学知识解决实际问题的能力。
二、实验任务
1、矩阵链相乘问题
2、投资问题
3、背包问题
4、TSP问题
旅行家要旅行n个城市,要求经历各个城市且仅经历一次,然后回到出发城市,并要求所走的路程最短。
5、数字三角形
问题描述:在上面的数字三角形中寻找一条从顶部到底边的路径,使得路径上所经过的数字之和最大。路径上的每一步都只能往左下或右下走。
三、实验设备及编程开发工具
笔记本,Windows10操作系统,Dev-C++,Pycharm
四、实验过程设计(算法设计过程)
(一)、矩阵链相乘问题
(1) 算法分析:
输入P=< P0, P1, ..., Pn>,Ai...j 表示乘积 AiAi+1...Aj 的结果,其最后一次相乘是:
m[i,j] 表示得到Ai...j的最少的相乘次数。
递推方程:
为了确定加括号的次序,设计表s[i,j],记录求得最优时最一位置。
(2) 算法实现:
python
import sys
import time
gk = lambda i, j: str(i)+','+str(j)
MAX = sys.maxsize # 获取最大的整数
def memorized_matrix_chain(p):
n = len(p)-1
m = {}
for i in range(1, n+1):
for j in range(i, n+1):
m[gk(i, j)] = MAX # 先初始化为最大值
return lookup_chain(m, p, 1, n)
def lookup_chain(m, p, i, j):
if m[gk(i, j)] < MAX:
return m[gk(i, j)]
if i == j:
m[gk(i, j)] = 0
else:
for k in range(i, j):
# 依次求出每段的最小的值 p = [P0,P1,......,Pn] 矩阵链 第一个矩阵为P0*P1
q = lookup_chain(m, p, i, k) + lookup_chain(m, p, k+1, j) + p[i-1]*p[k]*p[j]
# 找出最优的解
if q < m[gk(i, j)]:
m[gk(i, j)] = q
# 输出每一步的结果
# print("______")
# print(m)
return m[gk(i, j)]
def main():
p = [30, 35, 15, 5, 10, 20, 25, 5, 16, 34, 28, 19, 66, 34, 78, 55, 23]
# p = [30, 15, 10, 20, 5]
print(memorized_matrix_chain(p))
if __name__ == '__main__':
b = time.time()
main()
print('total run time is:', time.time()-b)
(二)、投资问题
(1) 问题分析:
子问题界定:由参数k和x界定
k: 考虑对项目1,2,...,k的投资
x: 投资总数不超过x
矩阵链相乘里边的i,j代表的是矩阵的下标,是同样的参数,这里两个参数k,x是不同的参数。
原始问题输入:
k=n,x=m
子问题的计算顺序:k=1,2,3,...,n
对于给定的k,计算x=1,2,3,...,m
优化函数的递推方程:
Fk(x): x元钱投资给前k个项目的最大收益
多步判断:
如果知道p元钱(0≤p≤x)投给前k−1个项目的最大效益Fk−1§,就可以进行x元钱投给前k个项目的最优决策
递推方程和边界条件:
Fk(x)=max0≤xk≤x{fk(xk)+Fk−1(x−xk)},1<k≤n
F1(x)=f1(x)
第一个项目投资决策: 投资第一个项目的钱不超过x,x∈[0,m]的最大收益就是f1(x),x∈[0,m]不需要计算.
第二项目投资决策: 第二个投资F2(x),是当xk,xk∈[0,x]元钱投资给第二个项目得到的效益加上x−xk元钱投资给前一个项目的最优.然后再在这里面取最大值,就是最优决策.直到决策
第n个项目的投资: 第n个投资Fn(x)是当xk,xk∈[0,x]元钱投资给第n个项目得到的效益加上x−xk元钱投资给前n−1个项目的最优.然后再在这里面取最大值,就是最优决策.
(2) 算法实现:
cpp
#define MAX_N 105 // 最大投资项目数目
#define MAX_M 105 // 最大投资钱数
int n,m; // n个项目,投资m万元
int f[MAX_N][MAX_M]; // 第i项投资j万元的收益 1 <= i <= n, 1 <= j <= m
int F[MAX_N][MAX_M]; // 投资前i项项目不超过j元钱的最大收益 1 <= i <= n, 1 <= j <= m
int x[MAX_N][MAX_M]; // 标记函数
// 打印计算结果
void printAns(){
int i,j;
for(i = 0; i <= n; i++){
for(j = 0; j <= m; j++){
printf("(%d,%d)\t", F[i][j], x[i][j]);
}
printf("\n");
}
printf("%d\n", F[n][m]);
}
// 动态规划算法实现, 时间复杂度O(nm^2)
void solve(){
int i,j,k,tmp;
for(i = 2; i <= n; i++) memset(F[i], -1, sizeof(int)*(m+1)); // 设置最大收益值的最小数目是-1
for(i = 0; i <= m; i++) F[1][i] = f[1][i]; // 投资第一个项目
for(i = 2; i <= n; i++){ // 投资前i个项目
F[i][0] = 0; // 投资前i个项目不超过0元的收益是0
x[i][0] = 0; // 标记
for(j = 1; j <= m; j++){ // 投资钱数不超过j
for(k = 0; k <= j; k++){ // 投资当前项目的钱数
tmp = f[i][k]+F[i-1][j-k];
if(tmp > F[i][j]){
F[i][j] = tmp; // 更新当前的最有解
x[i][j] = k; // 更新标记函数
}
}
}
}
printAns(); // 打印结果
}
int main(){
//freopen("in.txt", "r", stdin);
int i,j;
while(~scanf("%d%d", &n, &m) && n && m){ // 输入项目数,投资钱数
for(i = 1; i <= n; i++){ // 输入效益函数
f[i][0] = 0; // 投资0元的投资收益是0
for(j = 1; j <= m; j++) scanf("%d", &f[i][j]);
}
solve();
}
return 0;
}
(三)、背包问题
(1) 问题分析:
a) 把背包问题抽象化(X1,X2,...,Xn,其中Xi取0或1,表示第i个物品选或不选),Vi表示第i个物品的价值,Wi表示第i个物品的体积(重量);
b)建立模型,即求max(V1X1+V2X2+...+VnXn);
c)约束条件,W1X1+W2X2+...+WnXn<capacity;
d)定义V(i,j):当前背包容量j,前i个物品最佳组合对应的价值;
e)最优性原理是动态规划的基础,最优性原理是指"多阶段决策过程的最优决策序列具有这样的性质:不论初始状态和初始决策如何,对于前面决策所造成的某一状态而言,其后各阶段的决策序列必须构成最优策略"。
(2) 算法实现:
python
import numpy as np
# 行李数n,不超过的重量W,重量列表w和价值列表p
def fun(n, W, w, p):
a = np.array([[0] * (W + 1)] * (n + 1))
# 依次计算前i个行李的最大价值,n+1在n的基础上进行
for i in range(1, n + 1):
for j in range(1, W + 1):
if w[i - 1] > j:
a[i, j] = a[i - 1, j]
else:
a[i, j] = max(a[i - 1, j], p[i - 1] + a[i - 1, j - w[i - 1]]) # 2种情况取最大值
# print(a)
print('max value is:' + str(a[n, W]))
findDetail(p, n, a[n, W])
# 找到价值列表中的一个子集,使得其和等于前面求出的最大价值,即为选择方案
def findDetail(p, n, v):
a = np.array([[True] * (v + 1)] * (n + 1))
for i in range(0, n + 1):
a[i][0] = True
for i in range(1, v + 1):
a[0][i] = False
for i in range(1, n + 1):
for j in range(1, v + 1):
if p[i - 1] > j:
a[i, j] = a[i - 1, j]
else:
a[i, j] = a[i - 1, j] or a[i - 1, j - p[i - 1]]
if a[n, v]:
i = n
result = []
while i >= 0:
if a[i, v] and not a[i - 1, v]:
result.append(p[i - 1])
v -= p[i - 1]
if v == 0:
break
i -= 1
print(result)
else:
print('error')
# 初始化权重和价格
weights = [1, 2, 5, 6, 7, 9]
price = [1, 6, 18, 22, 28, 36]
# 设置背包的最大重量为13
fun(len(weights), 13, weights, price)
(四)TSP问题
(1) 问题分析:
假定从城市1出发,经过了一些地方,并到达了城市j。毋庸置疑,我们需要记录的信息有当前的城市j。同时我们还需要记录已经走过的城市的集合。同理,使用S记录未走过的城市的集合也可以的,且运算方便。
于是可以得出状态转移方程 go(S,init)=min{go(S−i,i)+dp[i][init]}∀s∈S
go(s,init)表示从init点开始,要经过s集合中的所有点的距离
因为是NP问题,所以时间复杂度通常比较大。使用dis[s][init]用来去重,初始化为-1,如果已经计算过init--->S(递归形式),则直接返回即可。
(2) 算法实现:
python
import pandas as pd
import numpy as np
import math
import time
# 读取城市的数据
dataframe = pd.read_csv("./data/TSP10cities.tsp", sep=" ", header=None)
v = dataframe.iloc[:, 1:3]
train_v= np.array(v)
train_d=train_v
dist = np.zeros((train_v.shape[0],train_d.shape[0]))
#计算距离矩阵
for i in range(train_v.shape[0]):
for j in range(train_d.shape[0]):
dist[i, j] = math.sqrt(np.sum((train_v[i,:]-train_d[j,:])**2))
"""
N:城市数
s:二进制表示,遍历过得城市对应位为1,未遍历为0
dp:动态规划的距离数组
dist:城市间距离矩阵
sumpath:目前的最小路径总长度
Dtemp:当前最小距离
path:记录下一个应该到达的城市
"""
N=train_v.shape[0]
path = np.ones((2**(N+1),N))
dp = np.ones((2**(train_v.shape[0]+1),train_d.shape[0]))*-1
def TSP(s, init, num):
if dp[s][init] != -1:
return dp[s][init]
if s == (1 << (N)):
return dist[0][init]
sumpath=1000000000
for i in range(N):
if s &(1<<i):
m = TSP(s&(~(1<<i)),i,num+1)+dist[i][init]
if m < sumpath:
sumpath = m
path[s][init]=i
dp[s][init] = sumpath
return dp[s][init]
if __name__ == "__main__":
init_point=0
s=0
for i in range(1,N+1):
s=s|(1<<i)
start = time.clock()
distance=TSP(s,init_point,0)
end = time.clock()
s=0b11111111110
init=0
num=0
print(distance)
while True:
print(path[s][init])
init=int(path[s][init])
s=s&(~(1<<init))
num+=1
if num>9:
break
print("程序的运行时间是:%s"%(end-start))
五、实验结果及算法复杂度分析
(一) 矩阵链相乘问题
1 实验结果
(二)投资最少问题
1 实验结果
时间复杂度 O ( n m 2 ) O(nm^2) O(nm2)
(三)背包问题
1 实验结果
(四)TSP问题
实验小结(包括问题和解决方法、心得体会等)
通过本次实验,利用C/C++、python语言编程实现解决了矩阵链相乘问题,投资问题,背包问题以及TSP问题。对于投资问题,python的实现没有实现出来,存在问题,使用了C语言成功实现。通过本次实验,也进一步加深的对动态规划算法的认识。
本次实验增加了动手编码能力,对动态规划算法有了更神的认识,同时也对算法设计有了更进一步的认识,但是技术上的缺陷,编码能力上存在的短板,在今后的实验中还需要加大练习力度。