笔记摘抄
废话:
不得不感叹链式前向星的强大,昨天下了点功夫学会了拓扑排序,理解了head[]和next的神奇用法后,没想到今天写专题遇到邻接矩阵无法建图的情况,就学了一下之前一直觉得没有用的spfa(),这一学收获颇多!!!
SPFA 算法详解(最短路径)
算法优点:
1:时间复杂度比普通的Dijkstra和ford低
2:能够计算负权图问题
3:能够判断是否存在负环(即:每跑一圈,路径会减少,所以会一直循环跑下去)
算法思想:
我们用数组记录每个结点的最短路径估计值,用邻接表来存储图G
我们采用的方法是动态逼近法:
1:设立一个先进先出的队列用来保存待优化的结点。
2:优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾
3:这样不断从队列中取出结点来进行松弛操作,直至队列空为止
期望的时间复杂度O(ke),其中k为所有顶点进队的平均次数,可以证明k一般小于等于2
实现方法:
1:存入图。可以用链式前向星或者vector
2:开一个队列,先将开始的结点放入
3:每次从队列中取出一个x,遍历与x相通的Y节点,查询比对Y的长度和X的长度+X与Y的长度
如果X的长度+X于Y的长度大于Y的长度,说明要进行更新操作
(1)存入最短路
(2)由于改变了原有的长度,所以需要往后更新,与这个节点相连的最短路。(判断下是否在队列,在就不用重复,不在就加入队列等待更新)
(3)在这期间可以记录这个节点的进队次数,判断是否存在负环
4直到队列空
判断有无负环:如果某个点进入队列的次数超过N次则存在负环
链式前向星+spfa( )实现最短路:
讲解
根据链式前向星的建图方法(这里涉及了next 和head[ ]),我们可以记录每个点指向了几个点
既然我们知道了每个点指向的几个点,这样就可以用我们的贪心算法去解决问题了
假如1-->
2 权值为2,1-->
3权值为3,所以我们就可以比较1-->
他连接的几个点的距离得到最短的距离然后把没有用过的点压入队列,根据贪心算法能很快的求出单源点到他想要的点的最短距离
例题:
题目:
方案一:普通迪杰斯特拉实现
AC代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
//邻接表建图
//Dijkstra
#define maxn 2005
#define inf 0x3f3f3f3f
int Map[maxn][maxn];//存图
int dis[maxn];//距离
int book[maxn];//做标记
int n;//点的个数
void Dijkstra(int u)//单源最短路
{
memset(book,0,sizeof(book));//清空数组
for(int i=1;i<=n;i++)
dis[i]=inf;//先让所有点是不可达到的点,赋值为inf
dis[u]=0;//让单源的点可达到
int minx,pos;//决斗出最小值的点
for(int i=1;i<=n;i++)
{
minx=inf;
for(int j=1;j<=n;j++)
{
if(!book[j]&&dis[j]<minx)
{
minx=dis[j];
pos=j;
}
}
book[pos]=1;//标记后下次不会再用该点
for(int k=1;k<=n;k++)
{
if(!book[k]&&dis[k]>dis[pos]+Map[pos][k])
{
dis[k]=dis[pos]+Map[pos][k];
}
}
}
}
int main()
{
int m;
while(~scanf("%d %d",&m,&n))
{
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
Map[i][j]=inf;
Map[i][i]=0;
}
int u,v,val;
while(m--)
{
scanf("%d %d %d",&u,&v,&val);
Map[u][v]=Map[v][u]=min(val,Map[u][v]);
}
//int st;
//scanf("%d",&st);
Dijkstra(1);//单源点到其他位置的最短路
//int en;
//scanf("%d",&en);
printf("%d\n",dis[n]);
}
}
方案二:链式前向星
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
//链式前向星优化spfa
#define maxn 10005
struct node
{
//int from;//起点
int to;//终点
int val;//权值
int next;//寻址
}graph[maxn];
int book[maxn];
int head[maxn];
int dis[maxn];
//int book[maxn];
int n,m,cnt;
void input(int u,int v,int w)//输入
{
graph[cnt].to=v;
graph[cnt].val=w;
graph[cnt].next=head[u];//寻址操作
head[u]=cnt++;
}
void spfa(int s)
{
memset(dis,0x3f3f3f3f,sizeof(dis));
memset(book,0,sizeof(book));
dis[s]=0;
queue<int>q;
q.push(s);//将s入对
while(!q.empty())
{
int st=q.front();
q.pop();
book[st]=0;
for(int k=head[st];k!=-1;k=graph[k].next)//记录了单源点指向的点
{
int v=graph[k].to;//st所到的点
int w=graph[k].val;//st所到点的权值
if(dis[v]>dis[st]+w)
{
dis[v]=dis[st]+w;
if(book[v]==0)
{
book[v]=1;
q.push(v);
}
}
}
}
}
int main()
{
while(~scanf("%d %d",&m,&n))
{
cnt=0;
memset(head,-1,sizeof(head));
int u,v,w;
while(m--)
{
scanf("%d %d %d",&u,&v,&w);
input(u,v,w);
input(v,u,w);
}
//int s,en;
//scanf("%d",&s);
spfa(1);
//scanf("%d",&en);
printf("%d\n",dis[n]);
}
}