计 算 机 与信息科 学 系
课程设计报告
课程名称:算法分析课程设计 设计题目:动态规划算法的应用
专 业: 信息与计算科学 指导教师: 班 级:姓 名:
学 号: 20xx年6月2日
算法设计课程设计
目 录
一、课程设计目的 .................................................................................................................................. 1
二、设计课题:动态规划算法的应用 .................................................................................................. 1
1、实验平台 ........................................................................................................................................ 1
2、实验目的 ........................................................................................................................................ 1
3、动态规划算法的思想 .................................................................................................................... 1
4、解决的问题 .................................................................................................................................... 2 最优二叉树搜索 .............................................................................................................................. 2 最长公共子序列 .............................................................................................................................. 6 防卫导弹 .......................................................................................................................................... 8 滑雪问题 .......................................................................................................................................... 9 矩阵连乘问题 ................................................................................................................................ 12 凸多边形最优三角问题 ................................................................................................................ 15 最大子段和 .................................................................................................................................... 17
三、设计总结 ........................................................................................................................................ 23
四、参考文献 ........................................................................................................................................ 23
算法设计课程设计
一、课程设计目的
1、本算法分析与设计课程设计是综合分析和理解动态规划算法,综合运用
C、C++或JAVA等程序设计语言,自行实现一系列的经典问题。
2、通过课程设计,自己更加熟练掌握算法的分析、算法设计、编程调试,写实验报告等环节,进一步掌握和灵活运用各种程序设计语言在问题实现中的应用 。
3、学会将知识应用于实际的方法,提高分析和解决问题的能力,增加综合能力。
总之,本课程设计的目的是使学生掌握算法分析与设计的一半规律和思想,以提高学生的编程能力和解决实际问题的能力。
二、设计课题:动态规划算法的应用
1、实验平台
VC++6.0
2、实验目的
通过实验,掌握面向对象编程的分析设计方法,以及与面向对象技术相关的一些软件开发技术,掌握在 VisualC++6环境下进行可视化程序设计技术。通过实践,为进一步开展相关领域的学习和科研打下良好的基础。
3、动态规划算法的思想:
1)、动态规划算法的基本要素
? 最优子结构
矩阵连乘计算次序问题的最优解包含着其子问题的最优解。这种性质称为最优子结构性质。在分析问题的最优子结构性质时,所用的方法具有普遍性:首先假设由问题的最优解导出的子问题的解不是最优的,然后再设法说明在这个假设下可构造出比原问题最优解更好的解,从而导致矛盾。 利用问题的最优子结构性质,以自底向上的方式递归地从子问题的最优解逐步构造出整个问题的最优解。最优子结构是问题能用动态规划算法求解的前提。 勤学 务实 圆融 卓越 1
算法设计课程设计
? 重叠子问题
递归算法求解问题时,每次产生的子问题并不总是新问题,有些子问题被反复计算多次。这种性质称为子问题的重叠性质。动态规划算法,对每一个子问题只解一次,而后将其解保存在一个表格中,当再次需要解此子问题时,只是简单地用常数时间查看一下结果。通常不同的子问题个数随问题的大小呈多项式增长。因此用动态规划算法只需要多项式时间,从而获得较高的解题效率。
2)、解题步骤
? 找出最优解的性质,并刻画其结构特征
? 递归地定义最优值
? 自底向上的方式计算出最优值
? 根据计算最优值是得到的信息,构造最优解。
4、解决的问题
1)、最优二叉树搜索
2)、最长公共子序列
3)、防卫导弹
4)、滑雪问题
5)、矩阵连乘最长公共子序列
6)、凸多边形最优三角问题
7)、最大子段和问题
8)、01背包问题
最优二叉树搜索
1. 问题描述
给定一个有序序列K={k1<k2<k3<,??,<kn}和他们被查询的概率P={p1,p2,p3,??,pn},要求构造一棵二叉查找树T,使得查询所有元素的总的代价最小。对于一个搜索树,当搜索的元素在树内时,表示搜索成功。当不在树内时,表示搜索失败,用一个“虚叶子节点”来标示搜索失败的情况,因此需要n+1个虚叶子节点{d0<d1<??<dn}。其中d0表示搜索元素小于k1的失败结果,dn表示搜索元素大于kn的失败情况。di(0<i<n)表示搜索节点在ki和k(i+1)之间时的失败情况。对于勤学 务实 圆融 卓越 2
算法设计课程设计
应di的概率序列是Q={q0,q1,??,qn}。
2. 问题分析:
在二叉树中T内搜索一次的期望代价为:
E[T]= (depth(ki)+1)*pi //对每个i=1~n,搜索成功情况+(depth(di)+1)*qi //对每个i=0~n,搜索失败情况。
3. 问题求解
步骤一:寻找最优子结构。
一个最优二叉树的子树必定包含连续范围的关键字ki~kj,1<=i<=j<=n,同时也必须含有连续的虚叶子节点di-1~dj。
如果一棵最优二叉查找树T有一棵含有关键字ki~kj的子树T',那么,T'也是一棵最优查找树,这通过剪贴思想可以证明。
现在开始构造最优子结构:在ki~kj中,选定一个r,i<=r<=j,使以kr为根,ki~k(r-1)和k(r+1)~kj为左右孩子的最优二叉树。注意r=i或者r=j的情况,表示左子树或右子树只有虚叶子节点。
步骤二:一个递归解。
定义e[i,j]为一棵包含关键字ki~kj的最优二叉树的期望代价。当j=i-1时没有真实的关键在,只有虚叶子节点d(i-1)。
于是:
当j=i-1时,e[i,i-1]=q(i-1)。
当j>=i时,需要选择合适的kr作为根节点,然后其余节点ki~K(r-1)和k(r+1)~kj构造左右孩子。这时要考虑左右孩子这些节点成为一个节点的子树后,它的搜索代价的变化:根据E[T]的计算,得知它们的期望代价增加了“子树中所有概率的总和”w。
w[i,j]= pl // 对每个l=i~j
+ql //对每个l=i-1~j
于是当j>=i时,e[i,j]=pr + (e[i,r-1]+w[i,r-1])+(e[r+1,j]+w[r+1,j]) = e[i,r-1] + e[r+1,j]+w[i,j];
步骤三:计算最优二叉树的期望代价
e[i,j]= q(i-1) //如果j=i-1
min(e[i,r-1] + e[r+1,j]+w[i,j]),如果i<=j,其中i<=r<=j
w[i,j] = q(i-1) 如果j=i-1
w[i,j]=w[i,j-1]+pj+qj 如果i<=j
3.代码实现
view plaincopy to clipboardprint?
勤学 务实 圆融 卓越 3
算法设计课程设计
1 #include <iostream>
2 using namespace std;
3 4 #define MAXNUM 100
5 #define MAX 65536
6 //p中为有序关键字k1到k5的搜索概率,k1<k2<k3<k4<k5
7 double p[MAXNUM] = {0.00,0.15,0.10,0.05,0.10,0.20};
8 double q[MAXNUM] = {0.05,0.10,0.05,0.05,0.05,0.10};
9 void optimal_bst(double e[][MAXNUM],int root[][MAXNUM],double w[][MAXNUM],int n)
10 {
11 int i =0,j=0;
12 //针对左或右孩子为空树情况初始化
13 for(i = 1;i<=n+1;i++)
14 {
15 e[i][i-1] = q[i-1];
16 w[i][i-1] = q[i-1];
17 }
18 int l = 0;
19 //计算顺序如下:根据计算式:e[i,j] = e[i,r-1]+e[r+1,j
首先计算节点个数为1的最优二叉树的代价e[1,1],e[2,2]?? 接着计算节点个数为1的最优二叉树的代价e[1,2],e[2,3]?? ??
最后计算结点个数为n的最优二叉树的代价e[1,n],利用之前保存的较少结点最优二叉树的结果。
20 for(l = 1;l<=n;l++)
21 {
22 for(i = 1;i<=n-l+1;i++)
23 {
24 j = i+l-1;
25 e[i][j] = MAX;
26 w[i][j] = w[i][j-1] + p[j]+q[j];
27 for(int r = i;r<=j;r++)
28 {
29 double t = 0;
勤学 务实 圆融 卓越 4
算法设计课程设计
30 t = e[i][r-1]+e[r+1][j] + w[i][j]; 31 if(t<e[i][j])
32 {
33 e[i][j]= t;
34 root[i][j] = r;
35 }
36 }
37
38 }
39 }
40
41 }
42 int main()
43 {
44 double e[MAXNUM][MAXNUM];
45 int root[MAXNUM][MAXNUM];
46 double w[MAXNUM][MAXNUM];
47
48 optimal_bst(e,root,w,5);
49
50 for(int i =1;i<=6;i++)
51 {
52 for(int j = 0;j<=5;j++)
53 {
54 cout << e[i][j] << " ";
55 }
56 cout << endl;
57 }
58 }
勤学 务实 圆融 卓越 5
算法设计课程设计
最长公共子序列
1)、问题描述
一个给定序列的子序列是在该序列中删去若干元素后得到的序列。确切地说,若给定序列X=<x1, x2,…, xm>,则另一序列Z=<z1, z2,…, zk>是X的子序列是指存在一个严格递增的下标序列 <i1, i2,…, ik>,使得对于所有j=1,2,…,k有
2)、算法分析
a) 最长公共子序列的结构
若用穷举搜索法,耗时太长,算法需要指数时间。
易证最长公共子序列问题也有最优子结构性质
设序列X=<x1, x2, …, xm>和Y=<y1, y2, …, yn>的一个最长公共子序列Z=<z1, z2, …, zk>,则:
i. 若xm=yn,则zk=xm=yn且Zk-1是Xm-1和Yn-1的最长公共子序列; ii. 若xm≠yn且zk≠xm ,则Z是Xm-1和Y的最长公共子序列; iii. 若xm≠yn且zk≠yn ,则Z是X和Yn-1的最长公共子序列。
其中Xm-1=<x1, x2, …, xm-1>,Yn-1=<y1, y2, …, yn-1>,Zk-1=<z1, z2, …, zk-1>。
最长公共子序列问题具有最优子结构性质。
3)、代码实现
import java.util.Scanner;
public class LSCLlength
{
static int b[][] = new int [100][100];
public static void main(String[] args)
{
String a;
String c;
Scanner scanner = new Scanner(System.in);
a= scanner.next();
c= scanner.next();
System.out.println("最长公共子序列的长度是:"+lcs_length(a,c)); System.out.print("最长公共子序列为:");
lcs(a.length()-1,a.length()-1,a,b);
}
勤学 务实 圆融 卓越 6
算法设计课程设计
public static int lcs_length(String x, String y ) {
int m,n,i,j; int l[][] = new int[100][100]; m=x.length()-1; n=y.length()-1; for(i=0;i<m+1;i++) l[i][0]=0; for(j=0;j<n+1;j++) l[0][j]=0; if(x.charAt(0)==y.charAt(0)) { l[0][0] = 1; b[0][0] =1; } for(i=1;i<=m;i++) for(j=1;j<=n;j++) if(x.charAt(i)==y.charAt(j)) { l[i][j]=l[i-1][j-1]+1; b[i][j]=1; } else if(l[i][j-1]>l[i-1][j]) { } else { } l[i][j]=l[i-1][j]; b[i][j]=3; l[i][j]=l[i][j-1]; b[i][j]=2;
勤学 务实 圆融 卓越 7
算法设计课程设计
}
static void lcs(int i,int j,String x,int [][] b)
{
if (i <0 || j<0) return ;
if (b[i][j]== 1){
lcs(i-1,j-1,x,b);
System.out.print(x.charAt(i));
}
else if (b[i][j]== 2) lcs(i-1,j,x,b);
else lcs(i,j-1,x,b);
}
} return l[m][n];
4)、运行结果
asdf
asdf
最长公共子序列的长度是:4
最长公共子序列为:asdf
防卫导弹
1)、问题描述
一种新型的防卫导弹可截击多个攻击导弹。它可以向前飞行,也可以用很快的速度向下飞行,可以毫无损伤地截击进攻导弹,但不可以向后或向上飞行。但有一个缺点,尽管它发射时可以达到任意高度,但它只能截击比它上次截击导弹时所处高度低或者高度相同的导弹。现对这种新型防卫导弹进行测试,在每一次测试中,发射一系列的测试导弹(这些导弹发射的间隔时间固定,飞行速度相同),该防卫导弹所能获得的信息包括各进攻导弹的高度,以及它们发射次序。现要求编一程序,求在每次测试中,该防卫导弹最多能截击的进攻导弹数量,一个导弹能被截击应满足下列两个条件之一:
a)它是该次测试中第一个被防卫导弹截击的导弹;
b)它是在上一次被截击导弹的发射后发射,且高度不大于上一次被截击导弹的高度的导弹。
输入数据:第一行是一个整数n,以后的n各有一个整数表示导弹的高度。 输出数据:截击导弹的最大数目。
2)、算法分析
勤学 务实 圆融 卓越 8
算法设计课程设计
定义l[i]为选择截击第i个导弹,从这个导弹开始最多能截击的导弹数目。 由于选择了第i枚导弹,所以下一个要截击的导弹j的高度要小于等于它的高度,所以l[i]应该等于从i+1到n的每一个j,满足h[j]<=h[i]的j中l[j]的最大值。
3)、代码实现
#include<stdio.h>
int main()
{
int i,j,n,max,h[100],l[100];
scanf("%d",&n);
for(i=0;i<n;i++)
scanf("%d",&h[i]);
l[n-1]=1;
for(i=n-2;i>=0;i--)
{
}
printf("%d",l[0]);
} max=0; for(j=i+1;j<n;j++) if(h[i]>h[j-1]&&max<l[j]) max=l[j]; l[i]=max+1;
滑雪问题
1)、问题描述
Michael喜欢滑雪百这并不奇怪, 因为滑雪的确很刺激。可是为了获得速度,滑的区域必须向下倾斜,而且当你滑到坡底,你不得不再次走上坡或者等待升降机来载你。Michael想知道载一个区域中最长底滑坡。区域由一个二维数组给出。数组的每个数字代表点的高度。下面是一个例子
1 2 3 4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9
一个人可以从某个点滑向上下左右相邻四个点之一,当且仅当高度减小。在勤学 务实 圆融 卓越 9
算法设计课程设计
上面的例子中,一条可滑行的滑坡为24-17-16-1。当然25-24-23-...-3-2-1更长。事实上,这是最长的一条。
Input
输入的第一行表示区域的行数R和列数C(1 <= R,C <= 100)。下面是R行,每行有C个整数,代表高度h,0<=h<=10000。
Output
输出最长区域的长度。
2)、算法分析
1、opt[x][y] = Max(opt[x-1][y], opt[x+1][y], opt[x][y-1],opt[x][y+1])
2、在该点周围的点中,只要其比该点更低,则该点所能滑的最长距离等于该点周围中能得最长距离加1,递归地执行这个操作,直至遍历每个点。然后找也能滑得的最长距离即为所求。
3、因为有备忘录,每个点所能滑的最长距离只求一次,算法复杂席为O(n)
3)、代码实现
#include <iostream>
using namespace std;
int matrix[100][100];
int cnt[100][100];
int row ,col;
int DP(int i, int j)
{
int max = 0;
if (cnt[i][j-1] > 0)
{
}
if (j-1 >= 0)
{
}
if (j+1 <= col-1)
勤学 务实 圆融 卓越 10 return cnt[i][j-1]; if (matrix[i][j] > matrix[i][j-1]) { } if (max < DP(i, j-1)) { } max = DP(i, j-1);
算法设计课程设计
{
}
if (i-1 >= 0)
{
}
if (i+1 <= row-1) {
}
return cnt[i][j] = max + 1; }
int main()
{ if (matrix[i][j] > matrix[i+1][j]) { } if (max < DP(i+1, j)) { } max = DP(i+1, j); if (matrix[i][j] > matrix[i-1][j]) { } if (max < DP(i-1, j)) { } max = DP(i-1, j); if (matrix[i][j] > matrix[i][j+1]) { } if (max < DP(i, j+1)) { } max = DP(i, j+1);
勤学 务实 圆融 卓越 11
算法设计课程设计
int i, j;
cin>>row>>col;
for (i=0; i<=row-1; i++)
{
}
for (i=0; i<=row-1; i++)
{
}
for (i=0; i<=row-1; i++)
{
}
cout<<cnt[0][0]<<endl; for (j=0; j<=col-1; j++) { } if (cnt[0][0] < cnt[i][j]) { } cnt[0][0] = cnt[i][j]; for (j=0; j<=col-1; j++) { } DP(i, j); for (j=0; j<=col-1; j++) { } cin>>matrix[i][j]; cnt[i][j] == 0;
return 0;
}
矩阵连乘问题
1)、问题描述
给定n个矩阵{A1,A2,…,An},其中Ai与Ai+1是可乘的,i=1,2 ,…,n-1。勤学 务实 圆融 卓越 12
算法设计课程设计
如何确定计算矩阵连乘积的计算次序,使得依此次序计算矩阵连乘积需要的数乘次数最少。
2)、算法分析
算法matrixChain的主要计算量取决于算法中对r,i和k的3重循环。循环体内的计算量为O(1),而3重循环的总次数为O(n3)。因此算法的计算时间上界为O(n3)。算法所占用的空间显然为O(n2)。
计算A[i:j]的最优次序所包含的计算矩阵子链 A[i:k]和A[k+1:j]的次序也是最优的。
矩阵连乘计算次序问题的最优解包含着其子问题的最优解。这种性质称为最优子结构性质。问题的最优子结构性质是该问题可用动态规划算法求解的显著特征。
3)、代码实现
import java.util.Scanner;
public class MartixMultiply {
static int m[][] = new int [100][100];
static int s[][] = new int [100][100];
public static void main(String[] args) { int n ; int [] matrix = new int [100]; Scanner scanner = new Scanner(System.in); System.out.println("请输入矩阵的个数!"); n= scanner.nextInt(); for(int i=0;i<=n;i++) { } matrixChain(matrix,m,s); } System.out.println("最少的矩阵相乘的乘法次数是:"+m[1][n]); System.out.println("最少的矩阵相乘的次序是:"); Traceback(1,n,s); matrix[i] = scanner.nextInt();
勤学 务实 圆融 卓越 13
算法设计课程设计
} public static void matrixChain(int [] p, int [][] m, int [][] s) { int n=p.length-1; for (int i = 1; i <= n; i++) m[i][i] = 0; for (int r = 2; r <= n; r++) for (int i = 1; i <= n - r+1; i++) { int j=i+r-1; m[i][j] = m[i+1][j]+ p[i-1]*p[i]*p[j]; s[i][j] = i; for (int k = i+1; k < j; k++) { int t = m[i][k] + m[k+1][j] + p[i-1]*p[k]*p[j]; if (t < m[i][j]) { m[i][j] = t; s[i][j] = k; } }}} public static void Traceback(int i,int j,int [][]s) { } if(i == j) return; Traceback(i,s[i][j],s); Traceback(s[i][j]+1,j,s); System.out.println("Multiply"+i+","+s[i][j]); System.out.println("and"+(s[i][j]+1)+","+j);
4)、运行结果
请输入矩阵的个数!
6
30 35 15 5 10 20 25
最少的矩阵相乘的乘法次数是:15125 最少的矩阵相乘的次序是:
勤学 务实 圆融 卓越 14
算法设计课程设计
Multiply2,2
and3,3
Multiply1,1
and2,3
Multiply4,4
and5,5
Multiply4,5
and6,6
Multiply1,3
and4,6
凸多边形最优三角问题
1)、问题描述
? 用多边形顶点的逆时针序列表示凸多边形,即P={v0,v1,?,vn-1}表示具有
n条边的凸多边形。
? 若vi与vj是多边形上不相邻的2个顶点,则线段vivj称为多边形的一条
弦。弦将多边形分割成2个多边形{vi,vi+1,?,vj}和{vj,vj+1,?vi}。
? 多边形的三角剖分是将多边形分割成互不相交的三角形的弦的集合T。 ? 给定凸多边形P,以及定义在由多边形的边和弦组成的三角形上的权函数
w。要求确定该凸多边形的三角剖分,使得即该三角剖分中诸三角形上权之和为最小。
2)、算法分析
? 定义t[i][j],1≤i<j≤n为凸子多边形{vi-1,vi,…,vj}的最优三角剖分所对应的
权函数值,即其最优值。为方便起见,设退化的多边形{vi-1,vi}具有权值0。据此定义,要计算的凸(n+1)边形P的最优权值为t[1][n]。
? t[i][j]的值可以利用最优子结构性质递归地计算。当j-i≥1时,凸子多边形
至少有3个顶点。由最优子结构性质,t[i][j]的值应为t[i][k]的值加上t[k+1][j]勤学 务实 圆融 卓越 15
算法设计课程设计
的值,再加上三角形vi-1vkvj的权值,其中i≤k≤j-1。由于在计算时还不知道k的确切位置,而k的所有可能位置只有j-i个,因此可以在这j-i个位置中选出使t[i][j]值达到最小的位置。由此,t[i][j]可递归地定义为:
?0? t[i ][j]?????min{t[i][k]t[k1][j]w(vvv)}?ikj1 ?i?k?j?3)、代码实现 i?ji?j
public class MinWeightTriangulation {
static int [][]grap={
{0, 14, 25, 27, 10, 11, 24, 16},
{14, 0, 18, 15, 27, 28, 16, 14},
} { } return grap[n1][n2]+grap[n1][n3]+grap[n2][n3]; public static int sk(int n1,int n2,int n3) {25, 18, 0, 19, 14, 19, 16, 10}, {27, 15, 19, 0, 22, 23, 15, 14}, {10, 27, 14, 22, 0, 14, 13, 20}, {11, 28, 19, 23, 14, 0, 15, 18}, {24, 16, 16, 15, 13, 15, 0, 27}, {16, 14, 10, 14, 20, 18, 27, 0} }; public static void main(String[] args) { System.out.println("最小的权值是:"+Triangulation(0,7));
public static int Triangulation(int k1,int k2)
{
int temp=100000; int t = 0; if(k2-k1==2) return sk(k1,k1+1,k2);
勤学 务实 圆融 卓越 16
算法设计课程设计
if(k2-k1==3) { if(grap[k1][k1+2]>grap[k2][k2-2]) return grap[k1][k1+1]+grap[k1+1][k1+2]+grap[k1+2][k2]+grap[k1][k2]+grap[k2][k2-2];
else return grap[k1][k1+1]+grap[k1+1][k1+2]+grap[k1+2][k2]+grap[k1][k2]+grap[k1][k1+2];
} } else { for(int i=k1+1;i<k2;i++) { if(i==k1+1) t=sk(k1+1,k1,k2)+Triangulation(k1+1,k2); if(i==k2-1) t=sk(k1+1,k2,k2-1)+Triangulation(k1+1,k2-1); if(i>k1+1&&i<k2-1) t=Triangulation(k1,i)+sk(k1,i,k2)+Triangulation(i,k2); if(temp>t) temp=t; } return temp; } }
4)、运行结果
最小的权值是:195
最大子段和
1)、问题描述
2)、算法分析
3)、代码实现
import java.util.Scanner;
public class MaxSum1 {
static double b=0;
public static void main(String[] args) { double a[] = new double [100]; int r;
勤学 务实 圆融 卓越 17
算法设计课程设计
double t; Scanner scanner = new Scanner(System.in); System.out.println("请输入该子段数字的个数:"); r = scanner.nextInt(); for(int i=0;i<r;i++) { } Maxsum(a,r-1); System.out.println("请输入该子段数字的最大字段和为:"+b); } a[i] = scanner.nextInt();
public static double Max(double a,double b)
{
}
public static void Maxsum(double x[],int r)
{
} double v=0; for(int i=0;i<r;i++) { } } v = Max(v+x[i],x[i]); if(b<v) b = v; if(a > b) return a; else return b;
4)、运行结果
?请输入该子段数字的个数:
8
1 -1 2 2 3 -3 4 -4
请输入该子段数字的最大字段和为:8.0
? 01背包问题
1)、问题描述
有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。勤学 务实 圆融 卓越 18
算法设计课程设计
求解将哪些物品装入背包可使价值总和最大。
2)、基本思路
这是最基础的背包问题,特点是:每种物品仅有一件,可以选择放或不放。 用子问题定义状态:即f[i][v]表示前i件物品恰放入一个容量为v的背包可以获得的最大价值。则其状态转移方程便是:
f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}
这个方程非常重要,基本上所有跟背包相关的问题的方程都是由它衍生出来的。所以有必要将它详细解释一下:“将前i件物品放入容量为v的背包中”这个子问题,若只考虑第i件物品的策略(放或不放),那么就可以转化为一个只牵扯前i-1件物品的问题。如果不放第i件物品,那么问题就转化为“前i-1件物品放入容量为v的背包中”,价值为f[i-1][v];如果放第i件物品,那么问题就转化为“前i-1件物品放入剩下的容量为v-c[i]的背包中”,此时能获得的最大价值就是f[i-1][v-c[i]]再加上通过放入第i件物品获得的价值w[i]。 优化空间复杂度
以上方法的时间和空间复杂度均为O(VN),其中时间复杂度应该已经不能再优化了,但空间复杂度却可以优化到O。
先考虑上面讲的基本思路如何实现,肯定是有一个主循环i=1..N,每次算出来二维数组f[i][0..V]的所有值。那么,如果只用一个数组f[0..V],能不能保证第i次循环结束后f[v]中表示的就是我们定义的状态f[i][v]呢?f[i][v]是由f[i-1][v]和f[i-1][v-c[i]]两个子问题递推而来,能否保证在推f[i][v]时(也即在第i次主循环中推f[v]时)能够得到f[i-1][v]和f[i-1][v-c[i]]的值呢?事实上,这要求在每次主循环中我们以v=V..0的顺序推f[v],这样才能保证推f[v]时f[v-c[i]]保存的是状态f[i-1][v-c[i]]的值。伪代码如下:
for i=1..N
for v=V..0
f[v]=max{f[v],f[v-c[i]]+w[i]};
其中的f[v]=max{f[v],f[v-c[i]]}一句恰就相当于我们的转移方程f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]},因为现在的f[v-c[i]]就相当于原来的f[i-1][v-c[i]]。如果将v的循环顺序从上面的逆序改成顺序的话,那么则成了f[i][v]由f[i][v-c[i]]推知,与本题意不符,但它却是另一个重要的背包问题P02最简捷的解决方案,故学习只用一维数组解01背包问题是十分必要的。 事实上,使用一维数组解01背包的程序在后面会被多次用到,所以这里抽象出一个处理一件01背包中的物品过程,以后的代码中直接调用不加说明。 过程ZeroOnePack,表示处理一件01背包中的物品,两个参数cost、weight勤学 务实 圆融 卓越 19
算法设计课程设计
分别表明这件物品的费用和价值。
procedure ZeroOnePack(cost,weight)
for v=V..cost
f[v]=max{f[v],f[v-cost]+weight}
注意这个过程里的处理与前面给出的伪代码有所不同。前面的示例程序写成v=V..0是为了在程序中体现每个状态都按照方程求解了,避免不必要的思维复杂度。而这里既然已经抽象成看作黑箱的过程了,就可以加入优化。费用为cost的物品不会影响状态f[0..cost-1],这是显然的。
有了这个过程以后,01背包问题的伪代码就可以这样写:
for i=1..N
ZeroOnePack(c[i],w[i]);
初始化的细节问题
我们看到的求最优解的背包问题题目中,事实上有两种不太相同的问法。有的题目要求“恰好装满背包”时的最优解,有的题目则并没有要求必须把背包装满。一种区别这两种问法的实现方法是在初始化的时候有所不同。 如果是第一种问法,要求恰好装满背包,那么在初始化时除了f[0]为0其它f[1..V]均设为-∞,这样就可以保证最终得到的f[N]是一种恰好装满背包的最优解。
如果并没有要求必须把背包装满,而是只希望价格尽量大,初始化时应该将f[0..V]全部设为0。
为什么呢?可以这样理解:初始化的f数组事实上就是在没有任何物品可以放入背包时的合法状态。如果要求背包恰好装满,那么此时只有容量为0的背包可能被价值为0的nothing“恰好装满”,其它容量的背包均没有合法的解,属于未定义的状态,它们的值就都应该是-∞了。如果背包并非必须被装满,那么任何容量的背包都有一个合法解“什么都不装”,这个解的价值为0,所以初始时状态的值也就全部为0了。
这个小技巧完全可以推广到其它类型的背包问题,后面也就不再对进行状态转移之前的初始化进行讲解。
一个常数优化
前面的伪代码中有 for v=V..1,可以将这个循环的下限进行改进。
由于只需要最后f[v]的值,倒推前一个物品,其实只要知道f[v-w[n]]即可。以此类推,对以第j个背包,其实只需要知道到f[v-sum{w[j..n]}]即可,即代码中的
for i=1..N
勤学 务实 圆融 卓越 20
算法设计课程设计
for v=V..0
可以改成
for i=1..n
bound=max{V-sum{w[i..n]},c[i]} for v=V..bound
这对于V比较大时是有用的。
3)、代码实现
import java.util.Scanner;
public class packages
{
static int w[]= new int[100]; static int v[]= new int[100]; static int r; static int t; public static void main(String[] args) { } Scanner scanner = new Scanner(System.in); System.out.println("输入物品的个数:"); r=scanner.nextInt(); System.out.println("输入物品的质量:"); for(int i = 0; i<r;i++) { } System.out.println("输入物品的价值:"); for(int j = 0; j<r;j++) { } System.out.println("输入背包的容纳量:"); t = scanner.nextInt(); System.out.println("最大的价值:"+knspsack(0,t)); v[j] = scanner.nextInt(); w[i] = scanner.nextInt();
勤学 务实 圆融 卓越 21
算法设计课程设计
public static int Max(int a,int b) {
}
public static int knspsack(int i,int t) {
}
}
if(i<r) { } else return 0; if(w[i]<=t) return Max(knspsack(i+1,t),knspsack(i+1,t-w[i])+v[i]); if(a>b) return a; else return b; else return knspsack(i+1,t);
4)、运行结果
输入物品的个数:
4
输入物品的质量:
5 3 4 8
输入物品的价值:
2 2 1 2
输入背包的容纳量:
9
最大的价值:4
勤学 务实 圆融 卓越 22
算法设计课程设计
三、设计总结
课程设计的过程也是一个不断摸索的过程。只有对所作题目有了清楚的认识和理解,有了思想上的充分准备,才能在设计过程中“胸有成竹”。所以我们对题目进行了比较详尽的考虑。当实际操作过程中遇到这样那样的困难,就通过查看资料、上网等方式解决。
通过这次课程设计,我们对动态规划算法有了更深的认识,同时也更加熟练了C、C++和JAVA程序设计语言的运用,是开发人员必不可少的有力工具。同时,我们学到了如何用算法的思想分析一个给定的问题,最终动手解决问题。在整个过程中,需要不断的调试,更改代码,当中,我们遇到了很多棘手问题。在不断思考、调试后,不仅锻炼了我的实际动手能力,更锻炼了我们发现问题、分析问题的能力。
四、参考文献
孙雄勇. 《Visual C++ 6.0 实用教程》. 北京: 中国铁道出版社,2004 谭浩强编著.《C++面向对象程序设计》.北京:清华大学出版社,2006 王晓东编著.《计算机算法设计与分析》.北京:电子工业出版社,2007.5 课程设计网(/)
勤学 务实 圆融 卓越 23