算法设计与分析多段图最短路径问题Word文件下载.docx
《算法设计与分析多段图最短路径问题Word文件下载.docx》由会员分享,可在线阅读,更多相关《算法设计与分析多段图最短路径问题Word文件下载.docx(16页珍藏版)》请在冰豆网上搜索。
我们以E所有边的集合,而边的权重则由权重函数w:
E?
→?
[0,?
∞]定义。
因此,w(u,v)就是从顶点u到顶点v的非负花费值(cost)。
边的花费可以想像成两个顶点之间的距离。
任两点间路径的花费值,就是该路径上所有边的花费值总和。
已知有V中有顶点s及t,Dijkstra算法可以找到s到t的最低花费路径.?
最短路径)。
这个算法也可以在一个图中,找到从一个顶点s到任何其他顶点的最短路径。
具体算法见附录。
2.动态规划法
这里先讨论用动态规划法的解法。
考虑多段图的最短路径问题的填表形式。
用一个数组cost[n]作为存储子问题解的表格,cost[i]表示从顶点i到终点n-1的最短路径,数组path[n]存储状态,path[i]表示从顶点i到终点n-1的路径上顶点i的下一个顶点。
则:
cost[i]=min{cij+cost[j]}(i≤j≤n且顶点j是顶点i的邻接点)(式)
path[i]=j(使cij+cost[j]最小的j)(式)
对多段图的边(u,v),用cuv表示边上的权值,将从源点s到终点t的最短路径记为d(s,t),则从源点0到终点9的最短路径d(0,9)由下式确定:
d(0,9)=min{c01+d(1,9),c02+d(2,9),c03+d(3,9)},这是最后一个阶段的决策,它依赖于d(1,9)、d(2,9)和d(3,9)的计算结果,而由此模式推知,d(1,9)=min{c14+d(4,9),c15+d(5,9)},d(2,9)=min{c24+d(4,9),c25+d(5,9),c26+d(6,9)},d(3,9)=min{c35+d(5,9),c36+d(6,9)},每一个d(i,n-1)都是通过min{cik+d(k,n-1)}得到的k>
i&
&
k<
n-1;
再往后推的过程和以上的过程类似,将这些产生式得到以后,会发现他们的求解除了两点之间的代价外,在例子中,他们都依赖于d(7,9)=c79和d(8,9)=c89,而他们都是可以从图上直接得到的。
这样再从末尾一层一层往上推就可以得到最终的答案了。
算法主要由三部分组成:
第一部分是初始化部分,其时间性能为O(n);
第二部分是依次计算各个顶点到终点的最短路径,由两层嵌套的循环组成,外层循环执行n-1次,内层循环对所有出边进行计算,并且在所有循环中,每条出边只计算一次。
假定图的边数为m,则这部分的时间性能是O(m);
第三部分是输出最短路径经过的顶点,其时间性能是O(n)。
所以,算法的时间复杂性为O(n+m)。
为了实现时间的分析,在程序后添加了输出运行时间的函数,以便于对比分析。
具体算法、具体代码及实验结果见附录1。
3.分支限界法
再讨论当用分支限界法用来解决多段图路径问题的过程:
首先对该多段图应用贪心法求得近似解,并算出其代价路径。
将其作为多段图最短路径问题的上界。
而把每一段最小的代价相加,可以得到一个非常简单的下界。
于是,就可以得到了目标函数的一个大致的范围。
由于多段图将顶点划分为k个互不相交的子集,所以,多段图划分为k段,一旦某条路径的一些段被确定后,就可以并入这些信息并计算部分解的目标函数值的下界。
一般情况下,对于一个正在生成的路径,假设已经确定了i段(1≤i≤k),其路径为(r1,r2,…,ri,ri+1),此时,该部分解的目标函数值的计算方法即限界函数如下:
应用分支限界法同样求解附录中图所示多段图的最短路径问题,具体的搜索过程是这样进行的,首先考虑根节点,根据限界函数算出目标函数的值,然后下一个结点的选择在本例中有三种情况,这里每种情况下的目标函数值下界都要算出来并且加以比较,下界的计算方法为除了加上选定点与初始点之间的距离外,以后的第一个点选择一选定点为初始点到下段最小代价的路径,以后的段与段之间的代价都按他们之间最小的代价来计算。
这样再加上根节点与初始阶段之间的最小代价,就得到这种情况下的解了。
在得到的代价中,找出最小的代价,并以之为初始结点循环往下做,直到到达目标结点。
结论:
程序的运行截图如附录所示。
分析各个方法的整个过程得到以下思考:
1.贪心法、动态规划法和分支限界法都可以用来解决多段最短路径问题,然而在这种情况相比之下,贪心法的运算效率比较高,因为它不像另外两种方法一样,需要涉及到许多的点。
由于这里并没有找到函数有效地给程序计时(time函数很不精确,而对于小程序来说,就没有什么参考性)。
因此这里我们就以本题的数据为例,用一个笨方法,看各个方法访问了多少数据,可以看到,动态规划法由于需要填表,并有一个相关的迭代问题,它几乎涉及了所有的点;
而分支限界法,它通过贪心法设置的上下限,并以他们为依据进行剪枝,减少了许多的运算量。
而贪心法,访问了最少的点。
2.就结果准确性来看,就本题例子来看,贪心法结果为02479,路径的代价为20;
动态分配法采取的路径为:
03589,路径的代价为16;
而分支限界法,结果为03589,路径的代价为16。
可以看出,在这个方面,动态分配法和分支限界法都达到了预期的结果,相比直线,贪心法的误差就比较大了。
由以上的讨论,我们可以看出分支限界法的综合性能比较好,他和动态规划法在解决多段最短路径问题时都可以得到正确解,而贪心法虽然可以省时间与空间,但结果不准确是它的缺点。
各方法都是有利有弊的。
因此在选择方法时,还应当根据实际情况。
当只需要大概的一个解时,当然是要用省时省力的贪心法;
如果对结果又比较高的要求的话,那么就要采取动态规划法或分支限界法。
那么dijkstra算法呢,他的明显优点就是它的多用性,他可以求任意一点到其他某一点的距离,但是他访问的数据量很大,几乎要访问所有的边(相对于贪心法而言),因此这里来说,在单纯的解决多段最短路径问题时,他们的功能都差不多,而在解决其他较复杂的图时,Dijkstra算法有明显的优越性,但当然,作为贪心法的一种,他的结果的准确性不是那么的高。
Dijkstra算法在本质上为贪心算法,每一步的选择为当前步的最优,复杂度为O(n*n)。
动态规划法是可以看作是对分支限界法的改进。
分支限界算法,每一步的扩散为当前耗散度的最优,复杂度为(没算)
其实,他们各有各的优缺点,可以尝试将他们混合起来用,扬长避短,像动态规划法和分支限界法,我们是不是可以试着在动态规划法的过程中像分支限界法里一样,设置范围,并且过程中对肯定不会是最后结果的数据“剪枝”。
这样就可以提高运行速率了。
结论(必须精确、有条理、清晰与简要):
建议(直接从结论中得出):
附录
Dijstra算法(边的拓展){
While(!
(每一个d[v]==最短路径))
If(存在一条从u到v的边)
If(d[u]+w(u,v)<
d[v])
d[v]=d[u]+w(u,v);
用动态规划法解决多段图的最短路径算法为:
1.初始化:
数组cost[n]初始化为最大值,数组path[n]初始化为-1;
2.for(i=n-2;
i>
=0;
i--)
对顶点i的每一个邻接点j,根据式计算cost[i];
根据式计算path[i];
3.输出最短路径长度cost[0];
4.输出最短路径经过的顶点:
i=0
循环直到path[i]=n-1
输出path[i];
i=path[i];
用分支限界法求解多段图的最短路径问题的算法:
1.根据限界函数计算目标函数的下界down;
采用贪心法得到上界up;
2.将待处理结点表PT初始化为空;
3.for(i=1;
i<
=k;
i++)x[i]=0;
4.i=1;
u=0;
//求解第i段
5.while(i>
=1)
对顶点u的所有邻接点v
根据式计算目标函数值lb;
若lb<
=up,则将i,<
u,v>
lb存储在表PT中;
如果i==k-1且叶子结点的lb值在表PT中最小,
则输出该叶子结点对应的最优解;
否则,如果i==k-1且表PT中的叶子结点的lb值不是最小,则
up=表PT中的叶子结点最小的lb值;
将表PT中目标函数值超出up的结点删除;
u=表PT中lb最小的结点的v值;
i=表PT中lb最小的结点的i值;
i++;
动态规划法解决多段图最短路径问题:
/*多段图最短路径问题总结:
cost[i]表示从顶点i到终点n-1的最短路径,path[i]表示从顶点i到终点n-1的路径上顶点i的下一个顶点;
下面的公式重点:
cost[i]=min{c(ij)+cost[j]}
path[i]=使c(ij)+cost[j]最小的j;
c(ij)表示i和j顶点之间的距离*/
具体代码如下:
#include<
>
ctime>
#defineINFINITY32767
#defineMAX20
__int64start,end;
intmin[6],Part;
typedefstruct{
charvexs[MAX];
//顶点信息
intvexnum,arcnum;
intarcs[MAX][MAX];
//保存两个顶点之间的边长
}Graph;
//图的结构体
structnode{
intpart,node1,node2,lb,previous;
structnode*next;
};
voidCreateGraph(Graph&
G){//初始化多段图
inti,j;
start=clock();
printf("
请输入顶点数和边数:
"
);
//scanf("
%d%d"
&
;
=10;
//顶点数
=18;
//边的数
for(i=0;
i<
i++){
[i]=i;
}
for(j=0;
j<
j++)
[i][j]=INFINITY;
请按以下格式输入边的代价(顶点1顶点2两点之间边的代价,顶点标号从0开始):
\n"
for(k=0;
k++){
scanf("
%d%d%d"
i,j,[i][j]);
}
intgetDown(GraphG){//分支限界法求下界
intj,k,i,n,n0,down=0,initial[6][20];
//initial数组用来存储第i段有哪些结点
min[0]=INFINITY;
6;
20;
initial[i][j]=0;
j=0;
if[0][i]<
INFINITY){
initial[0][j++]=i;
if[0][i]<
min[0])
min[0]=[0][i];
}
Part=1;
down+=min[i];
i=0;
while(initial[i++][0]!
={
n=0;
j=0;
min[i]=INFINITY;
while(initial[i-1][j++]!
=0){
k=0;
n0=n;
while((k++)<
if[initial[i-1][j-1]][k-1]<
initial[i][n++]=k-1;
if[initial[i-1][j-1]][k-1]<
min[i])
min[i]=[initial[i-1][j-1]][k-1];
}
if(min[i]<
down+=min[i];
Part++;
returndown;
intGreedy(GraphG,intflag,intup){//贪心法
intmin=INFINITY,m=flag;
%d"
flag);
for(inti=0;
if[m][i]<
min){
min=[m][i];
flag=i;
if(flag<
{
printf("
->
up=Greedy(G,flag,up);
elseprintf("
%d\n"
returnup+min;
voidpath(GraphG,intup){//分支限界法
intdown,i,j,k,u,lb,flag=1,previous=0;
structnode*p,*end,*PT,*q,*ST,*End,*mins;
down=getDown(G);
\n若用分支限界法,该题的结果取值范围为[%d,%d]。
down,up);
PT=NULL;
end=NULL;
ST=NULL,End=NULL;
//PT用来存储合格的点,ST表用来存储由于拓展被删除的点
i=1;
u=0;
//求解第i段,u表示顶点,u是那个已确定的顶点
while(flag){
//对顶点u的所有邻接点v
//根据式计算目标函数值lb;
//若lb<
j++){//u=3,i=2
intmini=INFINITY;
//mini是用来存储选择的那个下个结点所连接的最短的路径
if(mini>
[u][j]){
for(k=0;
k++){//确定了结点以后找到由他出发最小的代价
if[j][k]<
mini)
mini=[j][k];
lb=[u][j]+mini;
for(intl=i+1;
l<
Part;
l++)
lb+=min[l];
//min[]数组存的是每段中最短的路径长度
intPrevious=previous;
intU=u;
if(U>
0){//计算lb
lb+=[Previous][U];
while(Previous!
p=PT;
while(p!
=NULL)
if(p->
node1==Previous&
p->
node2==U){
lb+=[Previous][U];
U=Previous;
Previous=p->
previous;
break;
}
}
if(lb<
=up){
structnode*p=newstructnode;
p->
part=i+1;
node1=u;
node2=j;
lb=lb;
next=NULL;
previous=previous;
printf("
%d->
%d,代价%d,上一个结点为%d\n"
p->
node1,p->
node2,p->
lb,p->
previous);
if(PT==NULL)PT=p;
elseend->
next=p;
end=p;
end->
}
q=PT;
lb=INFINITY;
while(q!
=NULL){
if(q->
lb<
lb){
mins=q;
lb=q->
lb;
q=q->
next;
}printf("
%d%dlb=%d"
mins->
node1,mins->
node2,lb);
if(p==q&
[end->
node2][]<
INFINITY){//输出该叶子结点对应的最优解
intdist=0,array[6]={0,0,0,0,0,0};
array[Part]=;
array[Part-1]=p->
node2;
array[Part-2]=p->
node1;
i=Part-3;
while(i>
array[i]=p->
i--;
q=PT;
while(q->
node2!
=array[p->
previous])
q++;
if(i>
0)
array[i-1]=q->
p=q;
printf("
最短路径为:
for(i=0;
printf("
array[i]);
if(i<
Part-1)
dist+=[i][i+1];
array[Part]);
用分支限界法得到的最短路径长度为:
dist);
flag=0;
break;
p=PT;
if(p->
node1==mins->
node1&
node2==mins->
node2){//删除PT链表中已被拓展的结点并把他添加到ST链表中
if(ST==NULL)
ST=p->
else
End->
next=p->
End=p->
End->
PT=PT->
//printf("
删除的结点是:
%d%d\n"
next->
node2);
/*else{
while(p->
next!
node2){
if(ST==NULL){
ST=p->
else{
End->
End=p->
p->