1.最短路径算法分为单源最短路径算法和多源最短路径算法。
1.1 单源最短路径算法,可以计算出从起点到任意一点的最短路径。例如Dijkstra算法和SPFA算法
1.2 多源最短路径算法,可以算出任意两点之间的最短路径。例如Floyd算法
1.3 负权边,就是指图中有边的权值为负,此时Dijkstra算法失效(无法计算出最短路径),但是SPFA算法和Floyd算法依然有效。
1.4 负权回路,也叫负权环。此时所有的最短路径算法全部失效,但是用SPFA算法可以检测出负权环,而Dijkstra和Floyd算法无法检测出负权环的存在。
2.Dijkstra和SPFA算法
2.1 Dijkstra算法的步骤:第一步选最小,第二步标记,第三步更新。
2.2 SPFA算法本质上是BFS算法,主要采用队列,其关键操作是出队和入队。
2.2.1 出队:每次队首的父节点出队
2.2.2 入队:初始时起点入队,之后孩子入队必须满足两个条件:
条件1:经过父节点能使孩子到起点的距离更短
条件2:孩子未在队中
2.3 代码示例:
#include <bits/stdc++.h>
using namespace std;
const int maxn=2500,maxm=6200;
const int INF=1<<30;
struct Edge
{
int v;
int w;
int next;
}e[maxm*2];
int en;
int head[maxn];
int n;//节点数
int m;//边数
int s;//起点
int t;//终点
void AddEdge(int u,int v,int w)
{
en++;
e[en].v=v;
e[en].w=w;
e[en].next=head[u];
head[u]=en;
}
//Dijkstra O(mlogn)
struct HeapNode//小根堆 ,存最小的
{
int u;//节点
int d;//节点u到起点的最短路径
//运算符重载函数
bool operator<(const HeapNode& rhs)const
{
return d>rhs.d; //小根堆
}
};
void Dijkstra()//使用优先队列优化的Dijkstra算法
{
int d[maxn];
priority_queue<HeapNode>q;//优先队列 小顶堆
for(int i=1;i<=n;i++)
{
d[i]=INF;
}
d[s]=0;
q.push((HeapNode){s,d[s]}) ;//起点入队
while(!q.empty())
{
HeapNode x=q.top();
q.pop();//父亲出队
int u=x.u;
if(x.d!= d[u])//?????????????????????????????
{
continue;
}
for(int i=head[u];i>=0;i=e[i].next)
{
int v=e[i].v,w=e[i].w;
if(d[u]+w<d[v])
{
d[v]=d[u]+w;
q.push((HeapNode){v,d[v]});
}
}
}
printf("%d\n",d[t]);
}
void SPFA()
{
int inq[maxn];//在队里的标志
int d[maxn];//存储到起点的最短路径
queue<int> q;
memset(inq,0,sizeof(inq));
for(int i=1;i<=n;i++)
{
d[i]=INF;
}
d[s]=0;//设置起点距离为零
q.push(s);//起点入队
inq[s]=1;//标记起点已经入队
while(!q.empty())
{
int u=q.front();//取出父节点
//cout<<u<endl;
q.pop();//父亲出队
inq[u]=0;//表示是否在队里面,0表示不在队里,1表示在队里
for(int i=head[u];i>=0;i=e[i].next)
{
int v=e[i].v,w=e[i].w;
if(d[v]>d[u]+w)//如果经过父亲能使u到子节点v的路径长度比原先v到起点的路径更短
{
d[v]=d[u]+w;
if(!inq[v])//如果v不在队里,就让v入队
{
q.push(v);//让v入队,标记v已入队
inq[v]=1; //标记
}
}
}
}
printf("%d\n",d[t]);
}
int main()
{
memset(head,-1,sizeof(head));
scanf("%d%d%d%d",&n,&m,&s,&t);
int u,v,w;
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&u,&v,&w);
AddEdge(u,v,w);
AddEdge(v,u,w);
}
//Dijkstra();
SPFA();
return 0;
}
/*
6 9
1 6
1 2 1
1 3 12
2 3 9
2 4 3
3 5 5
4 3 4
4 5 13
4 6 15
5 6 4
*/
3.Floyd算法
3.1 这个算法是最简单,功能最强大的最短路径算法,可以计算出任意两点之间的最短路径,但是缺点是时间复杂度高O(n^3)。
3.2 此算法必须背过(简单暴力)(“^_^”)
3.3 此算法的核心思想是插点法,如果经过k点能使i点到j点的最短路径变短,那么就经过k点。
3.4 此算法使用枚举法,一次插入一个点,更新任意两点之间的最短路径,即:
在任意两点之间插入1号点,看是否能更新最短路径
在任意两点之间插入2号点,看是否能更新最短路径
……
在任意两点之间插入n号点,看是否能更新最短路径
3.5 关键代码
3.6 代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=100;
const int INF=10000;
int n;//点的数量
int m;//边的数量
int g[maxn][maxn];//二维数组存图,邻接矩阵存图,数组的初始值全部为0
int d[maxn][maxn];//存储任意两点的最短路径长度
void insertEdge(int from,int to,int w)//边的起点终点和权值
{
g[from][to]=w;
}
void Floyd()
{
for(int k=1; k<=n; k++)//k为中间点 ,中间点必须为最外层循环,起点和终点可以交换
{
for(int i=1; i<=n; i++)//i为起点
{
for(int j=1; j<=n; j++)//j为终点
{
if(d[i][j]>d[i][k]+d[k][j])//经过k点能使i点到j点的最短路径变短
{
d[i][j]=d[i][k]+d[k][j];//更新
}
}
}
}
}
int main()
{
cin>>n>>m;
for(int i=1; i<=m; i++)
{
int from,to,w;
cin>>from>>to>>w;
insertEdge(from,to,w);
insertEdge(to,from,w);
}
//初始化d数组,若两点之间存有边初始化成权值大小,没有边则初始为无穷大
for(int i=1; i<=n; i++)
{
for(int j=1; j<=n; j++)
{
if(g[i][j]>0)//i到j之间有边
{
d[i][j]=g[i][j];
}
else
{
d[i][j]=INF;//i和j之间没有边相连
}
if(i==j)
{
d[i][j]=0;
}
}
}
Floyd();
//输出任意两点之间的最短路径
for(int i=1; i<=n; i++)
{
for(int j=1; j<=n; j++)
{
cout<<i<<"->"<<j<<":"<<d[i][j]<<endl;
}
}
return 0;
}
/*
6
9
1 2 1
1 6 2
2 3 4
2 6 4
3 4 2
3 6 1
4 5 3
4 6 3
5 6 5
*/