最短路径问题(Dijkstra算法、Floyd算法)
将近一天的时间(其实中间大部分看手机去了)终于把最短路径这个经典问题算搞懂了吧
一、Dijkstra算法
简单介绍:
Dijkstra算法(即迪杰斯特拉算法)时间复杂度(O(n^2))主要就是解决从单一源点到其他各点的最短路径问题;
基本思路:
设置两个顶点的集合:S和T(S+T=V)(也就是说S和T集合的元素个数的和为:总结点个数),集合S存放已找到最短路径的顶点,集合T存放当前还未找到最短路径的顶点。初始状态时,集合S中只包含源点v0,然后不断地从集合T中选取路径长度最短的顶点pos加入到集合S中,集合S每加入一个都要检测是否修改从源点到集合T中剩余顶点的最短路径长度值;集合T中各顶点新的最短路径长度值等于原来保存的最短路径长度与从源点到pos的最短路径长度加上从pos到当前顶点的路径长度之和的较小值
即:
if(vis[j]==0&&dis[j]>(dis[pos]+mp[pos][j]))
{
dis[j]=dis[pos]+mp[pos][j];
}
具体在下面说明;
相关数据结构:
int map[Maxsize][Maxsize]; //图的邻接矩阵
int vis[Maxsize]; //vis[j]=1表示该顶点已进入集合S,vis[j]=0表示未进入集合S
int dis[Maxsize]; //用来存储源点到各顶点的最短路径值
int path[Maxsize]; //用来存储当前节点的前驱,通过回溯课获得完整最短路径
例题:
输入
输入的第一行包含2个正整数n和s,表示图中共有n个顶点,且源点为s。其中n不超过50,s小于n。
以后的n行中每行有n个用空格隔开的整数。对于第i行的第j个整数,如果大于0,则表示第i个顶点有指向第j个顶点的有向边,且权值为对应的整数值;如果这个整数为0,则表示没有i指向j的有向边。当i和j相等的时候,保证对应的整数为0。
输出
只有一行,共有n-1个整数,表示源点至其它每一个顶点的最短路径长度。如果不存在从源点至相应顶点的路径,输出-1。
请注意行尾输出换行。
样例输入
4 1
0 3 0 1
0 0 4 0
2 0 0 0
0 0 1 0
样例输出
6 4 7
于是代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#define Maxsize 55
#define Max 9999999 //表示两点之间没有边
using namespace std;
int n,s; //其中n表示顶点个数、s表示源点(即:出发点)
int mp[Maxsize][Maxsize]; //图的邻接矩阵
int vis[Maxsize]; //vis[j]=1表示该顶点已进入集合S,vis[j]=0表示未进入集合S
int dis[Maxsize]; //用来存储源点到各顶点的最短路径值
int path[Maxsize]; //用来存储当前节点的前驱,通过回溯课获得完整最短路径
/**假设s是源点*/
void Dijkstra(int s)
{
memset(vis,0,sizeof(vis));
for(int i=0;i<n;i++)
{
dis[i]=mp[s][i];
path[i]=s;
}
vis[s]=1;
path[s]=-1;
int pos;
for(int i=0;i<n;i++)
{//主循环,每次求得源点到某个顶点j的最短路径,并将j设为以访问结点
int Min=Max+1; //为了将没有路径的点最终选上,初始化为Max+1
for(int j=0;j<n;j++) //从未求得最短路径点寻找最小D[j]
{
if(vis[j]==0&&dis[j]<Min) //顶点j未求得最短且当前路径最短
{
pos=j; //具有更小路径的点存储到j中
Min=dis[j];
}
}
vis[pos]=1; //标记结点pos以求得最短路径
for(int j=0;j<n;j++)
{
if(vis[j]==0&&(dis[pos]+mp[pos][j])<dis[j])
{
dis[j]=dis[pos]+mp[pos][j];
path[j]=pos; //记忆对应的路径,将j的前驱点改为i
}
}
}
/**通过回溯获得每个结点到源点的距离,以获得完整路径*/
/*for(int i=0;i<n;i++)
{
printf("%d: %d",dis[i],i);
int pre=path[i];
while(pre>=0)
{
printf("<-%d",pre);
pre=path[pre];
}
printf("\n");
}*/
}
int main()
{
scanf("%d%d",&n,&s);
/**初始化邻接矩阵(当i,j之间不存在边时,将其权值设为一个最大值Max(i==j除外))*/
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
scanf("%d",&mp[i][j]);
if(mp[i][j]==0&&i!=j) //表示当两点不存在边时,附一个最大值
{
mp[i][j]=Max;
}
}
}
Dijkstra(s);
for(int i=0;i<n;i++)
{
if(i!=s)
{
if(dis[i]!=Max)
{
printf("%d ",dis[i]);
}
else
printf("-1 "); //不存在最短路径时
}
}
return 0;
}
二、Floyd算法
简单介绍:
Floyd算法,计算每一对顶点之间的最短路径(相当于每次以一个顶点为源点,重复调用Dijkstra算法n次),故时间复杂度为O(n^3)
基本思路:
Floyd算法就是一个典型的动态规划问题,引入一个矩阵S,S[i][j]表示顶点i到顶点j的最短路径,初始时,矩阵S就是图的邻接矩阵,当顶点i、j之间没有边时,设一个最大值Max,然后不断的更新n次即可,同时也用一个矩阵p(p[i][j]初始化为j)
相关数据结构:
int dis[Maxsize][Maxsize]; //用来存放路径长度
int path[Maxsize][Maxsize]; //用来存放前驱结点,回溯以获得路径
int vis[Maxsize];
例题:
输入
输入的第一行包含1个正整数n,表示图中共有n个顶点。其中n不超过50。
以后的n行中每行有n个用空格隔开的整数。对于第i行的第j个整数,如果大于0,则表示第i个顶点有指向第j个顶点的有向边,且权值为对应的整数值;如果这个整数为0,则表示没有i指向j的有向边。当i和j相等的时候,保证对应的整数为0。
输出
共有n行,每行有n个整数,表示源点至每一个顶点的最短路径长度。如果不存在从源点至相应顶点的路径,输出-1。对于某个顶点到其本身的最短路径长度,输出0。
请在每个整数后输出一个空格,并请注意行尾输出换行。
样例输入
4
0 3 0 1
0 0 4 0
2 0 0 0
0 0 1 0
样例输出
0 3 2 1
6 0 4 7
2 5 0 3
3 6 1 0
于是代码如下(这里因为题目不需要知道完整路径长度,就没有用path[]数组了):
#include<cstdio>
#include<cstring>
#define Maxsize 55
#define Max 999999
using namespace std;
int n;
int dis[Maxsize][Maxsize];
int vis[Maxsize];
void Floyd()
{
for(int k=0; k<n; k++)
{
for(int i=0; i<n; i++)
{
for(int j=0; j<n; j++)
{
if(dis[i][j]>(dis[i][k]+dis[k][j]))
{
dis[i][j]=dis[i][k]+dis[k][j];
}
}
}
}
}
int main()
{
scanf("%d",&n);
for(int i=0; i<n; i++)
{
for(int j=0; j<n; j++)
{
scanf("%d",&dis[i][j]);
if(dis[i][j]==0&&i!=j)
dis[i][j]=Max;
}
}
Floyd();
for(int i=0; i<n; i++)
{
for(int j=0; j<n; j++)
{
if(i!=j&&dis[i][j]==Max)
printf("-1 ");
else
printf("%d ",dis[i][j]);
}
printf("\n");
}
return 0;
}
fighting!!!