int ContainerLoading( int x[], T w[], T c, int n ) { int *t = new int[n+1];
IndirectSort(w, t, n);//对重量按间接寻址方式排序 for( int i=1; i<=n; i++) x[i] = 0;
for(i=1; i<=n && w[t[i]]<=c; i++){ x[t[i]] = 1; //t[i]是间接寻址表 c -= w[t[i]]; }
6
复杂性分析:TnTnO1nc2TnOnnc2Onlogn delete t[];}
3)伪代码如下:
void knapsack(int v[ ], int w[ ], int c, int m[ ][ ]) { int n=v.length-1;
int jMax=min(w[n]-1,c);
for( int j=0; j<=jMax; j++) m[n][j]=0;
for( int j=w[n]; j<=2*w[n]-1; j++) m[n][j]=v[n];
for( int j=2*w[n]; j<=c; j++) m[n][j]=2*v[n];
for( int i=n-1; i>1; i--) { jMax=min(w[i]-1,c);
for( int j=0; j<=jMax; j++) m[i][j]=m[i+1][j];
for(int j=w[i]; j<=2*w[i]; j++)
m[i][j]=max(m[i+1][j], m[i+1][j-w[i]]+v[i]);
for(int j=2*w[i]; j<=c; j++)
m[i][j]=max(m[i+1][j], m[i+1][j-w[i]]+v[i], m[i+1][j-2*w[i]]+2*v[i] ); }
m[1][c]=m[2][c]; if (c>=2*w[1])
m[1][c]=max(m[1][c], m[2][c-w[1]]+v[1], m[2][c-2*w[1]]+2*v[1]); else if (c>=w[1])
m[1][c]=max(m[1][c], m[2][c-w[1]]+v[1]);} 显然,该算法能产生最优解。
二、习题(15、16、17章)
1、定义0/1/2背包问题为:max{ni1pixi}。限制条件为:
ni1wixic,且
xi{0,1,2},1in,f(i , y)表示剩余容量为y,剩余物品为:i,i+1,…,n时的最优解的
值。
1)推出f(i , y)的递推表达式(类似于15-1和5-2式);
2)请设计求解f(i , y)的算法,并用伪代码描述你的算法; 3)分析你的算法的复杂性。
4)设W=[10,20,15,30,10],P=[6,10,15,18,5],c=78,请用你的算法求解。 解答: 1)
max(f(i1,y),f(i1,ywi)vi,f(i1,y2*wi)2*vi,cy2*wif(i,y)max(f(i1,y),f(i1,ywi)vi),2*wiywif(i1,y) 0ywi
2vn,2wnycf(n,y)vn,wny2wn0,0ywn
7
2)
void knapsack(int v[ ], int w[ ], int c, int m[ ][ ]) {
int n=v.length-1;
int jMax=min(w[n]-1,c); for( int j=0; j<=jMax; j++) m[n][j]=0;
for( int j=w[n]; j<=2*w[n]-1; j++) m[n][j]=v[n];
for( int j=2*w[n]; j<=c; j++) m[n][j]=2*v[n];
for( int i=n-1; i>1; i--) {
jMax=min(w[i]-1,c);
for( int j=0; j<=jMax; j++)m[i][j]=m[i+1][j];
for(int j=w[i]; j<=2*w[i]; j++)
m[i][j]=max(m[i+1][j], m[i+1][j-w[i]]+v[i]);
for(int j=2*w[i]; j<=c; j++)
m[i][j]=max(m[i+1][j], m[i+1][j-w[i]]+v[i], m[i+1][j-2*w[i]]+2*v[i] ); }
m[1][c]=m[2][c]; if (c>=2*w[1])
m[1][c]=max(m[1][c], m[2][c-w[1]]+v[1], m[2][c-2*w[1]]+2*v[1]); else if (c>=w[1])
m[1][c]=max(m[1][c], m[2][c-w[1]]+v[1]); }
2、有形如下图所示的数塔,从顶部出发,在每一结点可以选择向左走或是向右走,一起走到底层,要求找出一条路径,使路径上的值最小。设计算法求解该问题
9
12 15
10 6 8 2 18 9 5
19 7 10 4 16
解答:程序如下:
#define Min(a,b) ((a)<(b)) ? a: b
#define max 104
int r[max],s[max][max],t[max] , dp[max][max]; int main()
{ int n , i , j , mx , ca , lo ,cnt ; scanf( \"%d\" , &ca ); while( ca-- )
{ scanf( \"%d\" , &n ); mx = 214000000;
8
for ( i = 1 ; i <= n ; i++ ) for ( j = 1 ; j <= i ; j++) scanf( \"%d\" , &s[i][j] ); dp[1][1] = s[1][1];
for(i = 2;i <= n;i++) {
{
for(j = 1;j <= i;j++) if(j == 1) dp[i][j] = dp[i-1][j]+s[i][j];
}
else if(j == i) dp[i][j] = dp[i-1][j-1]+s[i][j];
else dp[i][j]=Min(dp[i-1][j-1]+s[i][j],dp[i-1][j]+s[i][j]);}
for(i = 1;i <= n;i++)
if(dp[n][i] < mx) { lo = i; mx = dp[n][i]; }
cnt = n;
printf(\"%d\\n\while(cnt) {
r[cnt] = s[cnt][lo];
if(lo == cnt) lo--;
else if(lo > 1) { if(dp[cnt-1][lo-1] < dp[cnt-1][lo]) lo = lo-1; }
cnt--; } printf(\"%d\
for(i = 2;i <= n;i++) printf(\"-%d\
printf(\"\\n\"); } return 0;}
3、在用回溯法求解0/1背包问题时,
1) 画出该问题的解空间树;
2) 用伪代码描述用于剪枝的限界函数。
解答:
1)这个问题的解可以表示成0/1 数组(x1, x2, . . . , xn ),依据wi 是否属于S,xi 分别取值1 或0。故解空间中共有2^n 个元素。它的树结构是一棵完全二叉树。 解空间树
9
︰︰︰︰︰︰︰︰︰︰︰︰︰︰︰︰︰︰︰︰︰︰︰︰︰︰ 2) 限界函数:
double bound(int i)
{// 计算上界
double cleft = c - cw; // 剩余容量 double bound = cp;
// 以物品单位重量价值递减序装入物品 while (i <= n && w[i] <= cleft) {
cleft -= w[i]; bound += p[i]; i++; }
// 装满背包
if (i <= n) bound += p[i] / w[i] * cleft; return bound; }
5、给定n个作业的集合{J1,J2,…,Jn}。每个作业必须先由机器1处理,然后由机器2处理. 作业Ji需要机器j的处理时间为tji. 对于一个确定的作业调度,设Fji是作业i在机器j上完成处理的时间. 所有作业在机器2上完成处理的时间和称为该作业调度的完成时间和. 批处理作业调度问题要求对于给定的n个作业,制定最佳作业调度方案,使其完成时间和达到最小. 1) 画出该问题的解空间树; 2) 设计回溯算法求解该问题.
解答:1)解空间树为作业构成的排列数,4个作业的排列树如下所示:
10
2)public class FlowShop
static int n, // 作业数
f1, // 机器1完成处理时间 f, // 完成时间和 bestf; // 当前最优值
static int m[][]; // 各作业所需的处理时间 static int x[]; // 当前作业调度 static int bestx[]; // 当前最优作业调度 static int f2[]; // void backtrack(int i) { if (i > n) {
for(int j = 1; j <= n; j++) bestx[j] = x[j]; bestf = f; } else
for (int j = i; j <= n; j++)
{ f1+=m[x[j]][1];
f2[i]=((f2[i-1]>f1)?f2[i-1]:f1)+m[x[j]][2]; f+=f2[i];
if (f < bestf) { swap(x,i,j);
backtrack(i+1); swap(x,i,j); }
f1-=m[x[j]][1] f-=f2[i];
} }
6、 子集和问题:对于集合S={1,2,6,8},求子集,要求该子集的元素之和d=9。 a) 画出子集和问题的解空间树;
b) 对该树运用回溯算法,写出依回溯算法遍历节点的顺序表及节点。确定回溯算法未遍历的节点;
c) 如果S中有n个元素,指定d,用伪代码写出求解子集和问题的回溯算法。
11
解答:
1)这个问题的解可以表示成0/1 数组(x1, x2, . . . , xn ),依据wi 是否属于S,xi 分别取值1 或0。故解空间中共有2^n 个元素。它的树结构是一棵完全二叉树。
子集:{1,2 ,6},{1,8} 解空间树
2)深度优先:结点
0->1->3->7->15->7->16->7->3->8->17->8->18->8->3->1->4->9->19->9->20->9->4->10->21->10->22->10->4->1->0->2->5->11->23->11->24->11->5->12->25->12->26->12->5->2->6->13->27->13->28->13->6->14->29->14->30
3)按照回溯法思想,从状态树的根结点出发,做深度优先搜索; 1.为便于计算,将W中的正数按从小到大排序;
2.当在某一状态A下,依次尝试加入和不加入正数wi, 3.若∑A+wi>d,则可停止对该结点的搜索;
4.若∑A+ ∑(wi…wn) 伪代码算法如下:设置辅助向量A[i],记录wi是否被选入;
SW表示W的剩余和,SA表示选入项的总和;
初始化A[i] = -1,表示未被处理过, SW= ∑W, SA=0;
i=1;
while(i>0)
{ if (i>n) { if (SA==d) 输出 A[1..n]; i- -; } if (A[i]==-1) { A[i]=0;
SW- =wi; A[++i]=-1; } if(A[i]==0) { A[i]=1;
SA+=wi; A[++i]=-1;
if (SA==d) { 输出 A[1..n]; i--; } } If (A[i]==1)
{ A[i]=-1; SW += wi; SA - = wi; --i; } }
7、设有 n 件工作分配给 n 个人。将工作i分配给 j 个人所需的费用为 Cij. 现要求为每个人都分配1件不同的工作,并使总费用达到最小. 1) 画出该问题的解空间树; 2) 设计回溯算法求解该问题.
12
解答:
1) 该问题的解空间树是一棵排列树,4件工作分配给4个人的排列树如下:
2) 参考代码如下:
Void backtrack(int t) {
if ( t>n ) Compute(); else
for (int j=t; j<=n; j++) {
swap(r[t],r[j]); backtrack(t+1); swap(r[t],r[j]); } }
Void Compute()
{
for(int i=1, temp=0; i<=n;i++) temp+=p[i][r[i]]; if(tempbest=temp;for(i=1; i<=n; i++) bestr[i]=r[i]; } }
8、对于以下5 个矩阵:
A1: 105, A2: 54, A3: 49, A4: 910, A5: 102
求出这5个矩阵相乘需要的最小数乘次数及相应的加括号方式. (注:要求给出计算公式和计算过程)
解答:计算公式为:
0
m[i,j]min{m[i,k]mikj13 [k1,j]pi1pkpj}ijij
例如:
m[2][2]m[3][5]p1p2p5252542292m[2][5]minm[2][3]m[4][5]p1p3p5180180592 m[2][4]m[5][5]ppp5605102145m 1 2 3 4 5 1 0 200 560 960 392 2 0 180 560 292 3 0 360 252 4 0 180 5 0 S 1 2 3 4 5 1 0 1 1 1 1 2 0 2 3 2 3 0 3 3 4 0 4 5 0 加括号的方式为: (A1(A2(A3(A4A5))))
9、 旅行商问题:给出一个n个顶点网络(有向或无向),要求找出一个包含所有n个顶点的具有最小耗费的环路。任何一个包含所有n个顶点的环路被称作一个旅行。在旅行商问题中,要设法找到一条最小耗费的旅行。
1)对图示的例,画出旅行商问题的解空间树。 2)描述用FIFO分枝定界法解决上述问题的过程;
3)对有n个顶点的网络,用伪代码描述求解旅行商问题的FIFO分枝定界法。
伪代码如下
#include #include const int MaxSize=50; class Mgraph14
{
private:
int vertexNum, arcNum; //顶点数和边数 int vertex[MaxSize]; //顶点数组 int arc[MaxSize][MaxSize]; //邻接矩阵 int*visited; int i,j,k; public:
Mgraph(int a[], int n, int e);
void BFSTraverse(); //广度优先遍历 void BFSTraverse(int a); int link(int v); };
Mgraph::Mgraph(int a[], int n, int e) {
visited=new int[MaxSize]; vertexNum=n; arcNum=e;
for(i=0; ifor(i=0; ifor(k=0; kcout<<\"请输入边连接的两点:\";cin>>i>>j; //边依附的两个顶点的序号 arc[i][j]=1;
arc[j][i]=1; //置有边标志 }
cout<<\"输入完毕\"<void Mgraph::BFSTraverse( ) //广度优先遍历算法 {for(i=0;ifor(i=0;i15