最短路径问题(浙大MOOC学习笔记)
单源最短路(时间最短,路程最短,边的个数。。。):源点固定的
无权图
有权图
多源最短路:任意两个顶点之间
无权图的单源最短路
就是一次B F S,借助队列,很好实现
当然了,我们需要把BFS里面的Visitied 改一下,改为源点到各个点的最短距离dist
(初始化:正无穷,负无穷,-1)(与visitied的作用类似)
还有一个细节,我们需要用一个数组path,来存储路径
path存什么?
前一个顶点!
我们最后求解结束以后,从V点倒推到原点,利用堆栈,可以很好的实现反向。
伪码如下:
void Unweighted ( Vertex S )
{ Enqueue(S, Q);
while(!IsEmpty(Q)){
V = Dequeue(Q);
for ( V 的每个邻接点 W )
if ( dist[W] == -1 ) {
dist[W] = dist[V]+1;
path[W] = V;
Enqueue(W, Q);
}
}
}
这种方法的时间复杂度应该是O(V+E)
有权图的单源最短路
(首先声明:图里面没有负值圈,因为会挂,也暂时没有负边)
与无权图的算法的类似之处:路径的长度是依次递增的。
Dijkstra算法的基本描述
设V为点集,E为带权值的边集,v0 为原点,那么,我们进行如下的操作:
每次更新集合V的划分V1, V2 (其中V1是已经确定最短距离的点集)V2是待确定的点集
我们要做的:从V2里面取一个与v0距离最短的点p,放到V1里面。
更新所有与p邻接的点的与v0距离(即考虑以p作为中介顶点带来的影响)
若V2为空集,结束
算法细节的简化版证明
-
最短路径一定仅经过V1里面的顶点
pf:如果在V2里面存在顶点m,使得V1- m-p的长度小于V1-p,那么由于路径长度是从小到大递增的,导出m-V1的距离,小于p-V1,m应该在先于p出现在V1里面,矛盾!
-
每次p收到V1里面后,仅对V2里面与p邻接的点进行更新就可以了
pf:因为V1里面的点的距离已经是最短了,无需更新,而V2里面的点,由于p的最短距离的确定,会影响与p邻接的顶点与v0的已知最短距离,所以,更新与p邻接的顶点。
dist[W]=min(dist[W],dist[p]+<p,W>)
算法的实现细节
- V1 与V2的划分,我们利用collected数组实现
- 距离的存储我们利用dist数组实现,初始化应该利用正无穷
- path[W]=V ,来存路径。
伪码描述:
void Dijkstra( Vertex s )
{ while (1) {
V = 未收录顶点中dist最小者;
if ( 这样的V不存在 )
break;
collected[V] = true;
for ( V 的每个邻接点 W )
if ( collected[W] == false )
if ( dist[V]+E<V,W> < dist[W] ) {
dist[W] = dist[V] + E<V,W> ;
path[W] = V;
}
}
}
有权图的多源最短路
引入floyd算法
for(i=1; i<=v; i++)
{
for(j=1;j<=v;j++)
{D[i][j]=g[i][j];}//初始化D(-1)
}
for(k=1; k<=v; k++)
{
for(i=1; i<=v; i++)
{
for(j=1; j<=v; j++)
{
if((D[i][k]+D[k][j])<D[i][j])
{
D[i][j]=D[i][k]+D[k][j];
}
}
}
}
其中,如果想要导出路径,我们利用path来解决问题
i-j经过k
所以,找i-j转化为i-k与k-j
问题解决
最后,咱还得那几道题目来检验下自己的水平:
第一题哈利波特的考试
07-图4 哈利·波特的考试 (25分)
哈利·波特要考试了,他需要你的帮助。这门课学的是用魔咒将一种动物变成另一种动物的本事。例如将猫变成老鼠的魔咒是haha,将老鼠变成鱼的魔咒是hehe等等。反方向变化的魔咒就是简单地将原来的魔咒倒过来念,例如ahah可以将老鼠变成猫。另外,如果想把猫变成鱼,可以通过念一个直接魔咒lalala,也可以将猫变老鼠、老鼠变鱼的魔咒连起来念:hahahehe。
现在哈利·波特的手里有一本教材,里面列出了所有的变形魔咒和能变的动物。老师允许他自己带一只动物去考场,要考察他把这只动物变成任意一只指定动物的本事。于是他来问你:带什么动物去可以让最难变的那种动物(即该动物变为哈利·波特自己带去的动物所需要的魔咒最长)需要的魔咒最短?例如:如果只有猫、鼠、鱼,则显然哈利·波特应该带鼠去,因为鼠变成另外两种动物都只需要念4个字符;而如果带猫去,则至少需要念6个字符才能把猫变成鱼;同理,带鱼去也不是最好的选择。
输入格式:
输入说明:输入第1行给出两个正整数N (≤100)和M,其中N是考试涉及的动物总数,M是用于直接变形的魔咒条数。为简单起见,我们将动物按1~N编号。随后M行,每行给出了3个正整数,分别是两种动物的编号、以及它们之间变形需要的魔咒的长度(≤100),数字之间用空格分隔。
输出格式:
输出哈利·波特应该带去考场的动物的编号、以及最长的变形魔咒的长度,中间以空格分隔。如果只带1只动物是不可能完成所有变形要求的,则输出0。如果有若干只动物都可以备选,则输出编号最小的那只。
输入样例:
6 11
3 4 70
1 2 1
5 4 50
2 6 50
5 6 60
1 3 70
4 6 60
3 6 80
5 1 100
2 4 60
5 2 80
输出样例:
4 70
蛮简单的,直接floyd一遍,找出每行最小,再找出所有的最小
代码见下:
#include <iostream>
#include <queue>
#include <stdio.h>
#define MAXVALUE 99999
using namespace std;
int g[120][120];
int v,e;
void start()
{
cin>>v>>e;
int i,j,x,y,value;
for(i=0; i<e; i++)
{
cin>>x>>y>>value;
g[x][y]=value;
g[y][x]=value;
}
for(x=1; x<=v; x++)
{
for(y=1; y<=v; y++)
{
if(g[x][y]==0)
{
g[x][y]=MAXVALUE;
}
}
}
}
void floyd()
{
int i,j,k,n;
int D[120][120];
for(i=1; i<=v; i++)
{
for(j=1;j<=v;j++)
{D[i][j]=g[i][j];}//初始化D(-1)
}
for(k=1; k<=v; k++)
{
for(i=1; i<=v; i++)
{
for(j=1; j<=v; j++)
{
if((D[i][k]+D[k][j])<D[i][j])
{
D[i][j]=D[i][k]+D[k][j];
}
}
}
}
int animals[120],tempmax;
for(i=1; i<=v; i++)
{
tempmax=0;
for(j=1; j<=v; j++)
{
if(i!=j)
{
if(D[i][j]>tempmax)
{
tempmax=D[i][j];
}
}
}
animals[i]=tempmax;
}
int minvalue=MAXVALUE,minloca=-1,temp;
for(i=1; i<=v; i++)
{
if(minvalue>animals[i])
{
minvalue=animals[i];
minloca=i;
}
}
if(minloca==-1)
{
cout<<"0";
}
else
{
cout<<minloca<<" "<<minvalue;
}
}
int main()
{
start();
floyd();
}
练习2旅游规划
(居然一遍编译就AC了,233333)
蛮简单,把正常的Dij算法里面的path变成money,问题就结束了。。。
07-图6 旅游规划 (25分)
有了一张自驾旅游路线图,你会知道城市间的高速公路长度、以及该公路要收取的过路费。现在需要你写一个程序,帮助前来咨询的游客找一条出发地和目的地之间的最短路径。如果有若干条路径都是最短的,那么需要输出最便宜的一条路径。
输入格式:
输入说明:输入数据的第1行给出4个正整数N、M、S、D,其中N(2≤N≤500)是城市的个数,顺便假设城市的编号为0~(N−1);M是高速公路的条数;S是出发地的城市编号;D是目的地的城市编号。随后的M行中,每行给出一条高速公路的信息,分别是:城市1、城市2、高速公路长度、收费额,中间用空格分开,数字均为整数且不超过500。输入保证解的存在。
输出格式:
在一行里输出路径的长度和收费总额,数字间以空格分隔,输出结尾不能有多余空格。
输入样例:
4 5 0 3
0 1 1 20
1 3 2 30
0 3 4 10
0 2 2 20
2 3 1 20
输出样例:
3 40
代码如下:
#include <iostream>
#define inf 9999999
using namespace std;
struct way
{
int lenth;
int price;
/* data */
} graph[530][530];
int N,M,S,D;
int money[530];
int dist[530];
int collect[530];
void ini()
{
cin>>N>>M>>S>>D;
int i;
int x,y,l,p;
for(i=0;i<M;i++)
{
cin>>x>>y>>l>>p;
graph[x][y].lenth=l;
graph[x][y].price=p;
graph[y][x].lenth=l;
graph[y][x].price=p;
}
}
int find_mindist()
{
int i,tem=inf-5,rem=-1;
for(i=0;i<N;i++)
{
if(dist[i]<tem&&collect[i]==0)
{
tem=dist[i];
rem=i;
}
}
return rem;
}
int min(int a,int b)
{
if(a<b)
{return a;}
else
{
return b;
}
}
void da()
{
int i,j,k;
int p;
for(i=0;i<N;i++)
{
dist[i]=inf;
}
dist[S]=0;
money[S]=0;
while (1)
{
p=find_mindist();
if(p==-1)
{
break;
}
else
{
collect[p]=1;
for(i=0;i<N;i++)
{
if(graph[p][i].lenth!=0&&collect[i]==0)
{
if ((graph[p][i].lenth+dist[p])<dist[i])
{
dist[i]=graph[p][i].lenth+dist[p];
money[i]=money[p]+graph[p][i].price;
}
if ((graph[p][i].lenth+dist[p])==dist[i]&&money[i]>(money[p]+graph[p][i].price))
{
dist[i]=graph[p][i].lenth+dist[p];
money[i]=money[p]+graph[p][i].price;
}
}
}
}
}
cout<<dist[D]<<" "<<money[D];
}
int main()
{
ini();
da();
}