资源描述
资料内容仅供您学习参考,如有不当或者侵权,请联系改正或者删除。
课 程 设 计 报 告
课程设计名称: 算法设计与分析
系 : 三系
学生姓名: 吴 阳
班 级: 12软件(2)班
学 号: 0311232
成 绩:
指导教师: 秦川
开课时间: 年 一 学期
一、 问题描述
1.普通背包问题
给定n种物品和一个背包。物品i的重量是Wi,其价值为Vi,背包的容量为C。选择装入的背包的物品, 使得装入背包中的物品的总价值最大, 在选择物品i装入背包时, 能够选择物品i的一部分, 而不一定要全部装入背包, 1≤i≤n。
2.0/1背包问题
给定n种物品和一个背包。物品i的重量是Wi,其价值为Vi,背包的容量为C。选择装入的背包的物品, 使得装入背包中的物品的总价值最大, 在选择物品i装入背包时, 对于每种物品i只有两种选择, 即装入背包或者不装入背包, 不能将物品装入背包多次, 也不能只装入部分的物品i。
3.棋盘覆盖问题
在一个2k x 2k个方格组成的棋盘中恰有一个方格与其它的不同称为特殊方格, 想要求利用四种L型骨牌( 每个骨牌可覆盖三个方格) 不相互重叠覆盖的将除了特殊方格外的其它方格覆盖。
二、 问题分析
1.普通背包问题
对于背包问题, 若它的一个最优解包含物品j, 则从该最优解中拿出所含的物品j的那部分重量W, 剩余的将是n-1个原重物品1, 2, ······, j-1, j+1, ·····, n以及重为Wi-W的物品j中可装入容量为C-W的背包且具有最大价值的物品。
2.0/1背包问题
如果当前背包中的物品的总容量是cw, 前面的k-1件物品都已经决定好是否要放入包中, 那么第k件物品是否放入包中取决于不等式 cw + wk <= M (其中, wk为第k件物品的容量, M为背包的容量)( 此即约束条件)
然后我们再寻找限界函数, 这个问题比较麻烦, 我们能够回忆一下背包问题的贪心算法, 即物品按照 物品的价值/物品的体积 来从大到小排列, 然后最优解为( 1, 1, 1......., 1, t, 0, 0, ......) , 其中0<=t<=1;
因此, 我们在确定第k个物品到底要不要放入的时候(在前k-1个物品已经确定的情况下), 我们能够考虑我们能够达到的最大的价值, 即我们能够经过计算只放入一部分的k物品来计算最大的价值。我们要确保当前选择的路径的最大的价值要大于我们已经选择的路径的价值。这就是该问题的限界条件。经过该条件, 能够减去很多的枝条, 大大节省运行时间。
3.棋盘覆盖问题
每次都对分割后的四个小方块进行判断, 判断特殊方格是否在里面。这里的判断的方法是每次先记录下整个大方块的左上角方格的行列坐标, 然后再与特殊方格坐标进行比较, 就能够知道特殊方格是否在该块中。如果特殊方块在里面, 这直接递归下去求即可, 如果不在, 这根据分割的四个方块的不同位置, 把右下角、 左下角、 右上角或者左上角的方格标记为特殊方块, 然后继续递归。在递归函数里, 还要有一个变量s来记录边的方格数, 每次对方块进行划分时, 边的方格数都会减半, 这个变量是为了方便判断特殊方格的位置。其次还要有一个变nCount来记录L型骨牌的数量。
三、 建立数学模型
1.普通背包问题
普通背包问题的数学描述为: 在选择物品i装入背包时, 能够选择物品i的一部分, 而不一定要全部装入背包, 1≤i≤n。C>0,wi>0,vi>0,1≤i≤n,要求找出一个n元0-1向量( x1,x2,x3,·····, xn) ,xi∈{0,1}, 1≤i≤n,使得≤C,而且达到最大。
2.0/1背包问题
0-1背包问题的数学描述为: 不能将物品装入背包多次, 也不能只装入部分的物品i。 C>0,wi>0,vi>0,1≤i≤n,要求找出一个n元0-1向量( x1,x2,x3,·····, xn) ,xi∈{0,1}, 1≤i≤n,使得≤C,而且达到最大。
3.棋盘覆盖问题
当k>0时, 将2k×2k棋盘分割为4个2^k-1×2^k-1 子棋盘(a)所示。特殊方格必位于4个较小子棋盘之一中, 其余3个子棋盘中无特殊方格。为了将这3个无特殊方格的子棋盘转化为特殊棋盘, 能够用一个L型骨牌覆盖这3个较小棋盘的会合处, 如 (b)所示, 从而将原问题转化为4个较小规模的棋盘覆盖问题。递归地使用这种分割, 直至棋盘简化为棋盘1×1。
四、 算法设计
1.普通背包问题
因为每一个物品都能够分割成单位块, 单位块的利益越大显然总收益越大, 因此它局部最优满足全局最优, 能够用贪心法解决。
算法设计: 首先计算每种物品单位重量的价值Vi/Wi, 然后按单位重量价值从大到小进行排序, 根据贪心选择策略, 将尽可能多的单位重量价值最高的物品装入背包。或将这种物品全部装入背包后, 背包内的物品总重量未超过背包容量C, 则选择单位重量价值次高的物品并尽可能多地装入背包, 依此策略一直进行下去, 直到背包装满为止。
2.0/1背包问题
该0-1背包问题采用的是回溯算法, 回溯算法的基本解题步骤是:
( 1) 针对所给问题定义问题的解空间;
( 2) 确定易于搜索的解空间结构;
( 3) 以深度优先方式搜索解空间, 并在搜索过程中用剪枝函数避免无效的搜索。
算法设计:
a.物品有n种, 背包容量为C, 分别用p[i]和w[i]存储第i种物品的价值和重量, 用
x[i]标记第i种物品是否装入背包, 用bestx[i]存储第i种物品的最优装载方案;
b. 用递归函数Backtrack (i,cp,cw)来实现回溯法搜索子集树( 形式参数i表示递归深
度, n用来控制递归深度, 形式参数cp和cw表示当前总价值和总重量, bestp表示当前
最优总价值) :
① 若i >n, 则算法搜索到一个叶结点, 判断当前总价值是否最优:
1> 若cp>bestp, 更新当前最优总价值为当前总价值( 即bestp=cp) , 更新
装载方案( 即bestx[i]=x[i]( 1≤i≤n)) ;
② 采用for循环对物品i装与不装两种情况进行讨论( 0≤j≤1) :
1> x[i]=j;
2> 若总重量不大于背包容量( 即cw+x[i]*w[i]<=c) , 则更新当前总价 br=""> 值和总重量( 即cw+=w[i]*x[i],cp+=p[i]*x[i]) , 对物品i+1调用递归函
数Backtrack(i+1,cp,cw) 继续进行装载;
3> 函数Backtrack(i+1,cp,cw)调用结束后则返回当前总价值和总重量
( 即 cw-=w[i]*x[i],cp-=p[i]*x[i]) ;
4> 当j>1时, for循环结束;
③ 当i=1时, 若已测试完所有装载方案, 外层调用就全部结束;
c. 主函数调用一次backtrack(1,0,0)即可完成整个回溯搜索过程, 最终得到的bestp和bestx[i]即为所求最大总价值和最优装载方案。
3.棋盘覆盖问题
该棋盘算法用的是分治算法, 分治法解题的思路就是将大问题化为若干子问题, 再依次解决子问题, 最后获得问题的答案。
算法设计:
( 1) ChessBoard函数实现了递归的将棋盘划分为子棋盘, 并将棋盘进行覆盖。
( 2) main()函数用来进行输入棋盘的大小和特殊棋盘的位置。
( 3) 使用memset(Matrix,0,sizeof(Matrix))将Matrix[]数组清零。
五、 算法实现
1.普通背包问题
#include <stdio.h>
struct goodinfo
{
float p;//物品价值
float w;//物品重量
float x;//物品该放数量
int flag;//物品编码
};
void Insertionsort(goodinfo goods[],int n)
{
int i,j;
for (j=2;j<=n;j++)
{
goods[0]=goods[j];
i=j-1;
while (goods[0].p >goods[i].p )
{
goods[i+1]=goods[i];
i--;
}
goods[i+1]=goods[0];
}
}
//按物品效益重量比值做降序排列
void bag(goodinfo goods[],float M,int n)
{
float cu;
int i,j;
for(i=1;i<=n;i++)
goods[i].x=0;
cu=M;
for(i=1;i<=n;i++)
{
if(goods[i].w>cu)
break; //当物品重量大于剩余容量时跳出
goods[i].x =1;
cu=cu-goods[i].w ;//确定背包新的剩余容量
}
if(i<=n)
{
goods[i].x =cu/goods[i].w ;//该物品所要放的量
}
//按物品编号做降序排列
for(j=2;j<=n;j++)
{
goods[0]=goods[j];
i=j-1;
while (goods[0].flag <goods[i].flag )
{
goods[i+1]=goods[i];
i--;
}
goods[i+1]=goods[0];
}
printf("最优解为: \n");
for(i=1;i<=n;i++)
{
printf("第%d件物品要放: %f",goods[i].flag ,goods[i].x );
printf("\n");
}
}
void main()
{
printf("-----------运用贪心算法求解普通背包问题-------------\n");
int j,n;
float M;
goodinfo *goods;
while (j)
{
printf("请输入物品的总数量: ");
scanf("%d",&n);
goods= new struct goodinfo[n+1];
printf("请输入背包的最大容量: ");
scanf("%f",&M);
printf("\n");
for (int i=1;i<=n;i++)
{
goods[i].flag =i;
printf("请输入第%d个物品的重量: ",i);
scanf("%f",&goods[i].w );
printf("请输入第%d个物品的价值: ",i);
scanf("%f",&goods[i].p );
goods[i].p =goods[i].p /goods[i].w ;
printf("\n");
}
Insertionsort(goods,n);
bag(goods,M,n);
printf("如果继续则选择”1”, 如果结束则选择”0”\n");
scanf("%d",&j);
}
}
2.0/1背包问题
#include<stdio.h>
int n,c,bestp;//物品的个数, 背包的容量, 最大价值
int p[10000],w[10000],x[10000],bestx[10000];//物品的价值, 物品的重量, x[i]暂存物品的选中情况,物品的选中情况
void Backtrack(int i,int cp,int cw)
{ //cw当前包内物品重量, cp当前包内物品价值
int j;
if(i>n)//回溯结束
{
if(cp>bestp)
{
bestp=cp;
for(i=0;i<=n;i++) bestx[i]=x[i];
}
}
else
for(j=0;j<=1;j++)
{
x[i]=j;
if(cw+x[i]*w[i]<=c)
{
cw+=w[i]*x[i];
cp+=p[i]*x[i];
Backtrack(i+1,cp,cw);
cw-=w[i]*x[i];
cp-=p[i]*x[i];
}
}
}
int main()
{
int i;
bestp=0;
printf("请输入背包最大容量:\n");
scanf("%d",&c);
printf("请输入物品个数:\n");
scanf("%d",&n);
printf("请依次输入物品的重量:\n");
for(i=1;i<=n;i++)
scanf("%d",&w[i]);
printf("请依次输入物品的价值:\n");
for(i=1;i<=n;i++)
scanf("%d",&p[i]);
Backtrack(1,0,0);
printf("最大价值为:\n");
printf("%d\n",bestp);
printf("被选中的物品依次是(0表示未选中, 1表示选中)\n");
for(i=1;i<=n;i++)
printf("%d ",bestx[i]);
printf("\n");
return 0;
}
3.棋盘覆盖问题
#include <stdio.h>
#include <string.h>
int nCount = 0;
int Matrix[100][100];
void chessBoard(int tr, int tc, int dr, int dc, int size);
int main()
{
printf("注意: 矩阵大小为2的k次方!\n");
int y=1;
while(y)
{
int size,r,c,row,col;
memset(Matrix,0,sizeof(Matrix));
printf("请输入矩阵的大小:\n");
scanf("%d",&size);
printf("请输入特殊矩阵的坐标:\n");
scanf("%d%d",&row,&col);
chessBoard(0,0,row,col,size);
for (r = 0; r < size; r++)
{
for (c = 0; c < size; c++)
{
printf("%2d ",Matrix[r][c]);
}
printf("\n");
}
printf("如果继续则按”1”, 退出则按”0”: ");
scanf("%d",&y);
}
return 0;
}
void chessBoard(int tr, int tc, int dr, int dc, int size)
{
//覆盖左上角子棋盘
int s,t;
if (1 == size) return;
s = size/2; //分割棋盘
t = ++ nCount;//L型骨牌号
//特殊方格在此棋盘中
if (dr < tr + s && dc < tc +s)
{
chessBoard(tr,tc,dr,dc,s);
}
else
{
Matrix[tr+s-1][tc+s-1] = t;
chessBoard(tr,tc,tr+s-1,tc+s-1,s);
}
//locate the special grid on bottom left corner
if (dr < tr + s && dc >= tc + s )
{
chessBoard(tr,tc+s,dr,dc,s);
}
else
{
Matrix[tr+s-1][tc+s] = t;
chessBoard(tr,tc+s,tr+s-1,tc+s,s);
}
//locate the special grid on top right corner
if (dr >= tr + s && dc < tc + s)
{
chessBoard(tr+s,tc,dr,dc,s);
}
else
{
Matrix[tr+s][tc+s-1] = t;
chessBoard(tr+s,tc,tr+s,tc+s-1,s);
}
//locate the special grid on top left corner
if (dr >= tr + s && dc >= tc + s)
{
chessBoard(tr+s,tc+s,dr,dc,s);
}
else
{
Matrix[tr+s][tc+s] = t;
chessBoard(tr+s,tc+s,tr+s,tc+s,s);
}
}六、 测试分析
1.普通背包问题
( 1) 输出结果
( 2) 复杂度分析
时间复杂度为O(nlgn)
( 3) 问题及解决
2.0/1背包问题
( 1) 输出结果
( 2) 复杂度分析
计算上界需要O(n)时间, 在最坏的情况下有O(pow(2,n))个右儿子结点需要计算上界因此0-1背包问题的回溯算法所需的计算时间为O(*npow(2,n))。
( 3) 问题及解决
3.棋盘覆盖问题
( 1) 输出结果
( 2) 复杂度分析
设T( n) 是算法ChessBoard覆盖一个2^k *2^k棋盘所需要的时间, 则从算法的分治策略可知, T(k)满足如下递归方程: T(k)=
k=0
k>0 解得此递归方程可得T(k) = O( 4^k) 。由于覆盖一个2^k *2^k棋盘所需的L型骨牌个数为( 4^k — 1) /3,故算法ChessBoard是一个在渐进意义下最优的算法
( 3) 问题及解决
七、 结论
1.普通背包问题
普通背包问题采用的是贪心算法。
2.0/1背包问题
0-1背包问题采用的是回溯算法
3.棋盘覆盖问题
棋盘覆盖问题采用的是分治算法
总结所解决的问题( 采用的什么算法, 怎么设计的, 还存在哪些问题有待改进等内容) 。
八、 参考文献( 6个)
参考文献要注明作者、 出版社、 出版日期。如
[1] 申俊龙著.新编医院管理教程[M].北京: 科学出版社, : 25-163.
[2] Brian Henderson-Sellers. A Book of Object-Oriented Knowledge: An Introduction to Object-Oriented Software Engineering[M].Prentice-Hall, 1993: 39-113
展开阅读全文