算法问题描述
单源最短路:在一张有源点S的有向图中,求S到所有点的最短路
全图最短路(误):在一张有向图中,求所有点间的最短路
Dijstra算法
算法思想
本质是贪心算法,每次从走过的点集U中挑最小的dis[u]用dis[u]+u->v松弛没走过点集V中的dis[v]。
时间复杂度O(n^2)通常选择堆优化到O(nlogn)
正确性
想象在源点的时候,显然最短的那条路S->v1是一定要走出去的,不然从v’绕回来到v1时一定经过了比S-v1更长的路。把走过的点集看成一个点同理。
堆优化
显然可以用单调队列维护dis[u],取的时候用标记判断是不是已经取过即可,取过就跳过。
时间复杂度O(nlogn)
代码
struct NODE{
int u,w;
bool operator < (const NODE& rhs)const{
return w>rhs.w;
}
};
void Dijkstra(int s,int temp){
priority_queue<NODE> Q;
for(int i=1;i<=n;i++) dis[i]=INF,vis[i]=0;
dis[s]=0;
Q.push((NODE){s,0});
while(!Q.empty()){
NODE x=Q.top(); Q.pop();
int u=x.u;
if(vis[u]) continue;
vis[u]=1;//Dijstra在此处标记的目的是区分以走点集和未走点集,而非是否在队列中
for(int i=head[u];i!=-1;i=edge[i].next){
int v=edge[i].v,w=edge[i].w;
if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
Q.push((NODE){v,dis[v]});
}
}
}
}
SPFA算法
算法思想
本质是O(n*m)暴力松弛,实际复杂度玄学。
用队列维护将要进行松弛的点,使要松弛的点延迟松弛,让前面松弛的点可以先再优化一下后面要松弛的点,避免每能松弛就松弛,以来减少入队次数。
判负环
如果一个点入队超过n次则图中存在负环
代码
int SPFA(int s){
queue<int> Q;
memset(dis,0x3f,sizeof dis);
memset(vis,0,sizeof vis);
Q.push(s);dis[s]=0;vis[s]=1;
while(!Q.empty()){
int u=Q.front();
Q.pop();
vis[u]=0;
for(int i=head[u];i!=-1;i=edge[i].next){
int v=edge[i].v;
if(dis[u]+edge[i].w<dis[v]){
dis[v]=dis[u]+edge[i].w;
if(!vis[v]){
q.push(v);
vis[v]=1;//SPFA在此标记的目的是判断是否在队列中
}
}
}
}
}
Floyd算法
算法思想
本质是动态优化,将问题转化成从i号顶点到j号顶点只经过前k号点的最短路程。
dp[k][i][j]=min(dp[k−1][i][j],dp[k−1][i][k]+dp[k−1][k][j])
d
p
[
k
]
[
i
]
[
j
]
=
m
i
n
(
d
p
[
k
−
1
]
[
i
]
[
j
]
,
d
p
[
k
−
1
]
[
i
]
[
k
]
+
d
p
[
k
−
1
]
[
k
]
[
j
]
)
正确性
其实正确性挺显然的
代码
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
}
}
}