算法实验报告Word格式.docx
《算法实验报告Word格式.docx》由会员分享,可在线阅读,更多相关《算法实验报告Word格式.docx(27页珍藏版)》请在冰豆网上搜索。
/*将矩阵m的对角线位置上元素全部置0,此时应是r=1的情况,表示先计算第一层对角线上个元素的值*/
for(intr=2;
r<
r++)//r表示斜对角线的层数,从2取到n
{
for(inti=1;
=n-r+1;
i++)//i表示计算第r层斜对角线上第i行元素的值
{
intj=i+r-1;
//j表示当斜对角线层数为r,行下标为i时的列下标
m[i][j]=m[i+1][j]+p[i-1]*p[i]*p[j];
//计算当断开位置为i时对应的数乘次数
s[i][j]=i;
//断开位置为i
for(intk=i+1;
k<
j;
k++)
{
intt=m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j];
/*计算断开位置k为从i到j(不包括i和j)的所有取值对应的
(Ai*.....*Ak)*(Ak+1*.....Aj)的数乘次数*/
if(t<
m[i][j])
{
m[i][j]=t;
//将Ai*....Aj的最少数乘次数存入m[i][j]
s[i][j]=k;
//将对应的断开位置k存入s[i][j]
}
}
}
}
}
voidtraceback(inti,intj,ints[][N+1])//用递归来实现输出得到最小数乘次数的表达式
if(i==j)
printf("
A%d"
i);
else
("
);
traceback(i,s[i][j],s);
traceback(s[i][j]+1,j,s);
)"
voidmain()
intn;
//用来存储矩阵的个数
intq[2*N];
/*用q数组来存储最原始的输入(各矩阵的行和列),主要目的是为了检验这N个矩阵是否满足连乘的条件*/
intp[N+1],flag=1;
/*用p[i-1],p[i]数组来存储A的阶数,flag用来判断这N个矩阵是否满足连乘*/
intm[N+1][N+1];
//用m[i][j]二维数组来存储Ai*......Aj的最小数乘次数
ints[N+1][N+1];
//用s[i][j]来存储使Ai......Aj获得最小数乘次数对应的断开位置k
printf("
输入矩阵的个数(注:
小于100):
"
scanf("
%d"
&
n);
for(inti=0;
=2*n-1;
i++)//各矩阵的阶数的输入先存入数组q中接受检验
if(i%2==0)
printf("
————————\n"
*输入A%d的行:
(i/2)+1);
else
********列:
scanf("
q[i]);
for(i=1;
=2*n-2;
i++)//矩阵连乘条件的检验
if(i%2!
=0&
&
q[i]!
=q[i+1])
flag=0;
break;
for(intj=1;
j<
=n-1;
j++)
p[j]=q[2*j];
if(flag!
=0)
p[0]=q[0];
p[n]=q[2*n-1];
matrixChain(p,m,s);
式子如下:
\n"
traceback(1,n,s);
最少数乘次数为%d\n"
m[1][n]);
这%d个矩阵不能连乘!
n);
四、结果截图
五、结果分析
说明,输入的行和列时遇到重复的数只输入一次即可,这就是为什么程序运行例子中8个矩阵相乘,输入的是9的数据的原因了。
值得注意的是,编程中使用的数据是int类型的,所以矩阵个数不能太大,不然最后结果超出了int的表示范围而发生错误。
程序优劣比较,程序完成了问题要求的计算,但是在输入的时候不太符合平时的习惯,不太让人满意,如果规模很大,则需要改动下程序。
(二)0/1背包问题:
现有n种物品,对1<
=i<
=n,已知第i种物品的重量为正整数Wi,价值为正整数Vi,背包能承受的最大载重量为正整数W,现要求找出这n种物品的一个子集,使得子集中物品的总重量不超过W且总价值尽量大。
(注意:
这里对每种物品或者全取或者一点都不取,不允许只取一部分)
一、算法分析
根据问题描述,可以将其转化为如下的约束条件和目标函数:
于是,问题就归结为寻找一个满足约束条件
(1),并使目标函数式
(2)达到最大的解向量
。
首先说明一下0-1背包问题拥有最优解。
假设
是所给的问题的一个最优解,则
是下面问题的一个最优解:
如果不是的话,设
是这个问题的一个最优解,则
,且
因此,
,这说明
是所给的0-1背包问题比
更优的解,从而与假设矛盾。
二、多种解法
1)穷举法:
用穷举法解决0-1背包问题,需要考虑给定n个物品集合的所有子集,找出所有可能的子集(总重量不超过背包重量的子集),计算每个子集的总重量,然后在他们中找到价值最大的子集。
设4个物品和一个容量为10的背包,下图是用穷举法求解0-1背包问题的过程。
(a)四个物品和一个容量为10的背包
序号
子集
总重量
总价值
1
空集
9
{2,3}
7
52
2
{1}
42
10
{2,4}
8
37
3
{2}
12
11
{3,4}
65
4
{3}
40
{1,2,3}
14
不可行
5
{4}
25
13
{1,2,4}
15
6
{1,2}
54
{1,3,4}
16
{1,3}
{2,3,4}
{1,4}
{1,2,3,4}
19
(b)用回溯法求解0-1背包问题的过程
2)递归法:
利用递归法解决0-1背包问题,从第n个物品看起。
每次的递归调用都会判断两种情况:
(1)背包可以放下第n个物品,则x[n]=1,并继续递归调用物品重量为W-w[n],物品数目为n-1的递归函数,并返回此递归函数值与v[n]的和作为背包问题的最优解;
(2)背包放不下第n个物品,则x[n]=0,并继续递归调用背包容量为W,物品数目为n-1的递归函数,并返回此递归函数值最为背包问题的最优解。
递归调用的终结条件是背包的容量为0或物品的数量为0.此时就得到了0-1背包问题的最优解。
用递归法解0-1背包问题可以归结为下函数:
第一个式子表示选择物品n后得到价值
比不选择物品n情况下得到的价值
小,所以最终还是不选择物品n;
第二个式子刚好相反,选择物品n后的价值
不小于不选择物品n情况下得到了价值
,所以最终选择物品n。
在递归调用的过程中可以顺便求出所选择的物品。
下面是标记物品被选情况的数组x[n]求解的具体函数表示:
在函数中,递归调用的主体函数为KnapSack,m表示背包的容量,n表示物品的数量,x[n]表示是否选择了第n个物品(1—选,0—不选)。
每个物品的重量和价值信息分别存放在数组w[n]和v[n]中。
3)贪心法:
0-1背包问题与背包问题类似,所不同的是在选择物品
装入背包时,可以选择一部分,而不一定要全部装入背包。
这两类问题都具有最优子结构性质,相当相似。
但是背包问题可以用贪心法求解,而0-1背包问题却不能用贪心法求解。
贪心法之所以得不到最优解,是由于物品不允许分割,因此,无法保证最终能将背包装满,部分闲置的背包容量使背包单位重量的价值降低了。
4)动态规划法分析:
0-1背包问题可以看作是寻找一个序列
,对任一个变量
的判断是决定
=1还是
=0.在判断完
之后,已经确定了
,在判断
时,会有两种情况:
(1)背包容量不足以装入物品i,则
=0,背包的价值不增加;
(2)背包的容量可以装下物品i,则
=1,背包的价值增加
这两种情况下背包的总价值的最大者应该是对
判断后的价值。
令
表示在前i
个物品中能够装入容量为j
的背包的物品的总价值,则可以得到如下的动态规划函数:
式
(1)说明:
把前面i个物品装入容量为0的背包和把0个物品装入容量为j的背包,得到的价值均为0。
式
(2)第一个式子说明:
如果第i个物品的重量大于背包的容量,则装入第i个物品得到的最大价值和装入第i-1个物品得到的最大价值是相同的,即物品i不能装入背包中;
第二个式子说明:
如果第i个物品的重量小于背包的容量,则会有两种情况:
(1)如果把第i个物品装入背包,则背包中物品的价值就等于把前i-1个物品装入容量为
的背包中的价值加上第i个物品的价值
;
(2)如果第i个物品没有装入背包,则背包中物品的价值就是等于把前i-1个物品装入容量为j的背包中所取得的价值。
显然,取二者中价值较大者作为把前i个物品装入容量为j的背包中的最优解。
第一步,只装入第一个物品,确定在各种情况下背包能得到的最大价值;
第二步,只装入前两个物品,确定在各种情况下的背包能够得到的最大价值;
一次类推,到了第n步就得到我们所需要的最优解。
最后,
便是在容量为W的背包中装入n个物品时取得的最大价值。
为了确定装入背包的具体物品,从
的值向前寻找,如果
>
说明第n个物品被装入了背包中,前n-1个物品被装入容量为
的背包中;
否则,第n个物品没有装入背包中,前n-1个物品被装入容量为W的背包中。
依此类推,直到确定第一个物品是否被装入背包为止。
由此,我们可以得到如下的函数:
根据动态规划函数,用一个
的二维数组C存放中间变量,
表示把前i个物品装入容量为j的背包中获得的最大价值。
设物品的重量存放在数组w[n]中,价值存放在数组v[n]中,背包的容量为W,数组
存放迭代的结果,数组x[n]存放装入背包的物品。
5)回溯法分析:
用回溯法解0_1背包问题时,会用到状态空间树。
在搜索状态空间树时,只要其左儿子结点是一个可行结点,搜索就进入其左子树。
当右子树有可能包含最优解时才进入右子树搜索,否则将右子树剪去。
设r是当前剩余物品价值总和;
cp是当前价值;
bestp是当前最优价值。
当cp+r≤bestp时,可剪去右子树。
计算右子树中解的上界可以用的方法是将剩余物品依其单位重量价值排序,然后依次装入物品,直至装不下时,再装入该物品的一部分而装满背包。
由此得到的价值是右子树中解的上界,用此值来剪枝。
为了便于计算上界,可先将物品依其单位重量价值从大到小排序,此后只要顺序考察各物品即可。
在实现时,由MaxBoundary函数计算当前结点处的上界。
它是类Knap的私有成员。
Knap的其他成员记录了解空间树种的节点信息,以减少函数参数的传递以及递归调用时所需要的栈空间。
在解空间树的当前扩展结点处,仅当要进入右子树时才计算上界函数MaxBoundary,以判断是否可以将右子树减去。
进入左子树时不需要计算上界,因为其上界与父结点的上界相同。
在调用函数Knapsack之前,需要先将各物品依其单位重量价值从达到小排序。
为此目的,我们定义了类Objiect。
其中,
运算符与通常的定义相反,其目的是为了方便调用已有的排序算法。
在通常情况下,排序算法将待排序元素从小到大排序。
在搜索状态空间树时,由函数Backtrack控制。
在函数中是利用递归调用的方法实现了空间树的搜索。
三、时空效率分析
对于一个有n个元素的集合,其子集数量为
,所以,不论生成子集的算法效率有多高,穷举法都会导致一个
的算法。
在递归法的算法体中有一个if判断中出现了两次递归调用比较大小所以它们之间的递归关系式可以大体表示为:
,其中
表示递归法的时间复杂度,C是常数。
求解递归方程可以知道
的量级为
所以递归法解0-1背包问题的时间复杂度为
递归法是耗费空间最多的算法,每次递归调用都需要压栈,导致栈的使用很频繁。
3)动态规划法:
由于函数Knapsack中有一个两重for循环,所以时间复杂度为O[(n+1)x(m+1)].空间复复杂度也是O[(n+1)x(m+1)],即O(nm).
4)回溯法:
由于计算上界的函数MaxBoundary需要O(n)时间,在最坏情况下有
个右儿子结点需要计算上界,所以解0-1背包问题的回溯法算法BackTrack所需要的计算时间为
.
四、运行结果
递归法输出结果:
动态规划法输出结果:
回溯法输出结果:
五、分析输出结果
上面测试的是每种算法在两种输入情况下得到的0-1背包问题的解。
两种测试数据为:
第一组:
背包容量:
18;
物品数目:
7;
每个物品重量为:
112489610;
每个物品价值为:
7881213414。
第二组:
50;
10;
81224166935211819;
34325667543245564670。
四种实现的算法中,只有回溯法没能够得到预期的最优解。
(但是可能是算法设计时的问题,其实回溯法是穷举法的变形,肯定能够得到最优解的,这里是我设计函数的问题。
从递归法的输出可知,它的结果就是我们想要的最优解)。
从时间复杂度和空间复杂度分析可知,动态规划法的时间复杂度是最小的,但是同时它的空间复杂度又是最大的。
这里就可以看出在设计算法的过程中要考虑它们的平衡问题。
在时间要求比较快的情况下,我们就可以选择动态规划法;
在空间要求比较高时,我们就可以使用穷举法或是分枝限界法等其他改进的穷举法。
各种算法在解背包问题时的比较如下表所示:
算法名称
时间复杂度
优点
缺点
改进
穷举法
最优解
速度慢
剪枝
递归法
空间消耗大
用数组存
动态规划法
递归方程求解
贪心法
不一定是最优解
速度快
可以作为启发
回溯法
改进剪枝
分枝限界法
优化限界函数
从计算复杂性理论看,背包问题是NP完全问题。
半个多世纪以来,该问题一直是算法与复杂性研究的热门话题。
通过对0-1背包问题的算法研究可以看出,回溯法和分枝限界法等可以得到问题的最优解,可是计算时间太慢;
动态规划法也可以得到最优解,当
时,算法需要
的计算时间,这与回溯法存在一样的缺点——计算速度慢;
采用贪心算法,虽然耗费上优于前者,但是不一定是最优解。
目前,以上几种方法中回溯法、动态规划法、贪心法都广泛地应用到不同的实际问题中,并在应用中不断地改进。
六、算法代码
1)递归法
#include"
stdafx.h"
#include<
iostream.h>
stdlib.h>
dosmax.h"
//hasmax()andmin()
intp[6]={0,6,3,5,4,6};
//p价值,多放置一个0,是为了让下标统一,方便理解和计算。
intw[6]={0,2,2,6,5,4};
//w重量
//intx[6];
intn=5;
//所有物品的数量
intc=10;
//实际最大总容量
//F函数的返回值是,当前剩为余容量为y,并且当前仍有剩余物品从i到n时,的最优解。
//i为当前检索到哪个物品的序号
intF(inti,inty)
if(i==n)return(y<
w[n])?
0:
p[n];
//最终返回点,一次性比较搞定!
if(y<
w[i])returnF(i+1,y);
//如果y小于当前重量,那么只有一个选择:
继续向下搜寻,看看能不能放下后面的物品
//如果y大于当前物品的重量w[i],就有两个选择了(虽然不能当场计算出这两个选择的值,但是没关系,让它们继续往下计算就是了):
//最后返回(假设当前不放入物品,y的值不变)和(假设当前放入物品,y减去当前物品的重量)的两种不同选择之后,所造成不同价值的比较结果。
//在i=n之前,所有的F函数代表的临时总价值,都是悬而未决的。
但是一旦i=n之后,依次返回正确的值。
//F(i+1,y)和F(i+1,y-w[i])+p[i],它们都是i+1时候的情况,分头进行计算,相互不依赖。
层次分解,就好象是一颗二叉树(中间如果y<
w[i]就只有一个节点)。
//最后只得出一个F的值(最优值),其余F的临时总价值,全部丢弃。
returnmax(F(i+1,y),F(i+1,y-w[i])+p[i]);
//切记,返回的是物品的总价值(最大值=最优解)
voidmain(void)
cout<
<
"
Optimalvalueis"
;
F(1,c)<
endl;
//=============================dosmax.h=====================================================
#ifndefdosmax_
#definedosmax_
template<
classtype>
inlinetypemax(typea,typeb)
return(a>
b)?
a:
b;
inlinetypemin(typea,typeb)
return(a<
#endif
//==================================================================================
//
如果下标从0开始,那么只需要改碰到n的情况,因为没有w[n]和p[n]存在。
但当前w[i]与p[i]不变。
intp[5]={6,3,5,4,6};
intw[5]={2,2,6,5,4};
if(i==n-1)return(y<
w[n-1])?
p[n-1];
//casd'
arret,一次性比较搞定!
F(0,c)<
2)动态规划法
/************************************************************************
*
0/1背包问题求解
(visual
studio
2005)
给定一个载重量为m,及n个物品,其重量为wi,价值为vi,1<
=n
要求:
把物品装入背包,并使包内物品价值最大
/
************************************************************************/
#include
s