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]]; }
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背包问题为:。条件为:,且,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)
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;
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]; }
printf("%d\\n",mx); cnt = 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",r[1]);
for(i = 2;i <= n;i++) printf("-%d",r[i]);
printf("\\n");
}
return 0;}
3、在用回溯法求解0/1背包问题时,
1)画出该问题的解空间树;
2)用伪代码描述用于剪枝的限界函数。
解答:
1)这个问题的解可以表示成0/1 数组(x1, x2, . . . , xn ),依据wi 是否属于S,xi 分别取值1 或0。故解空间有2^n 个元素。它的树结构是一棵完全二叉树。
解空间树
︰︰︰︰︰︰︰︰︰︰︰︰︰︰︰︰︰︰︰︰︰︰︰︰︰︰
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个作业的排列树如下所示:
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,用伪代码写出求解子集和问题的回溯算法。
解答:
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) 设计回溯算法求解该问题.
解答:
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(temp { best=temp;
for(i=1; i<=n; i++) bestr[i]=r[i];
}
}
8、对于以下5 个矩阵:
A1: 105, A2: 54, A3: 49, A4: 910, A5: 102
求出这5个矩阵相乘需要的最小数乘次数及相应的加括号方式.
(注:要求给出计算公式和计算过程)
解答:计算公式为:
例如:
m 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#includeconst int MaxSize=50;
class Mgraph
{
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; i vertex[i]=a[i]; for(i=0; i for(j=0; j arc[i][j]=0; for(k=0; k { cout<<"请输入边连接的两点:";
cin>>i>>j; //边依附的两个顶点的序号
arc[i][j]=1;
arc[j][i]=1; //置有边标志
}
cout<<"输入完毕"<} void Mgraph::BFSTraverse( ) //广度优先遍历算法
{
for(i=0;i visited[i]=0; for(i=0;i if(visited[i]==0) BFSTraverse(i);
}