这一篇博客主要是最短路四大方法模板的归纳
一、Floyd
佛洛伊德是最简单的最短路径算法,可以计算图中任意两点间的最短路径。时间复杂度为O(N3),适用于出现负边权的情况。
算法描述:
( a )初始化:点u、v如果有边相连,则dis[u][v]=w[u][v]
如果不相连,则dis[u][v]=0x3f
( b )模板:
for(k=1;k<=n;k++){
for(i=1;i<=n;i++){
for(j=1;j<=n;j++){
if(dis[i][j]>dis[i][k]+dis[k][j])
dis[i][j]=dis[i][k]+dis[k][j];
}
}
}
( c )算法结束:dis[i][j]得出的就是任意起点i到任意终点j的最短路径。
完整代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int M=1005;
int dis[M][M];
int main(){
int n,m;
scanf("%d %d",&n,&m);//n代表点数,m代表边数
for(int i=1;i<=m;i++){
int s,t,w;
scanf("%d %d %d",&s,&t,&w);
dis[s][t]=w;
// dis[t][s]=w;如是无向图则加上这一行
}
int S,T;
scanf("%d %d",&S,&T);
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(dis[i][j]>dis[i][k]+dis[k][j])
dis[i][j]=dis[i][k]+dis[k][j];
}
}
}
printf("%d",dis[S][T]);
}
二、Dijkstra
结点分成两组:已经确定最短路、尚未确定最短路不断从第2组中选择路径长度最短的点放入第1组并扩展本质是贪心,只能应用于正权图
算法描述:
设起点为s,dis[v]表示从指定起点s到v的最短路径,pre[v]为v的前驱,用来输出路径
(a)初始化
memset(dis,+∞),memset(vis,0);
(v:1~n)dis[v]=w[s][v],bool vis[v]=0;
dis[s]=0;pre[s]=0;vis[s]=1;
(b)
for(i=1;i<=n-1;i++)
1 在没有被访问过的点中找一个相邻顶点k,使得dis[k]是最小的;
2 k标记为已确定的最短路vis[k]=true;
3 用for循环更新与k相连的每个未确定最短路径的顶点v (所有未确定最短路的点都松弛更新)
if(dis[k]+w[k][v]<dis[v]) dis[v]=dis[k]+w[k][v],pre[v]=k;
(c)算法结束dis[v]为s到v的最短路距离;pre[v]为v的前驱结点,用来输出路径。
Dijkstra 分为三种类型:
① 普通代码
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int M=2005;
int G[M][M];
int dis[M];
bool flag[M];
int main(){
int n,m;
scanf("%d %d",&n,&m);
for(int i=0;i<=n;i++){
for(int j=0;j<=n;j++){
G[i][j]=0x3f3f3f3f;
}
dis[i]=0x3f3f3f3f;
G[i][i]=0;
}//赋初值,最好不要使用memset
for(int i=1;i<=m;i++){
int s,t,w;
scanf("%d %d %d",&s,&t,&w);
G[s][t]=min(G[s][t],w);
//G[t][s]=min(G[t][s],w);如果是双向加
}
int S,T;
scanf("%d %d",&S,&T);
dis[S]=0,flag[S]=true;
for(int i=1;i<=n;i++){
int id=0;
for(int j=1;j<=n;j++){
if((!flag[j])&&(id==0||dis[id]>dis[j])){
id=j;
}
}
flag[id]=true;
for(int j=1;j<=n;j++){
dis[j]=min(dis[j],dis[id]+G[id][j]);
}
}
printf("%d",dis[T]);
}
② 邻接表代码
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int M=1005;
int n,m;
int dis[M];
bool flag[M];
struct edge{
int v,w;
edge(){}
edge(int V,int W){
v=V,w=W;
}
};
vector<edge> G[M];
int Dijkstra(int S,int T){
memset(dis,0x3f,sizeof(dis));
dis[S]=0;
for(int i=1;i<=n;i++){
int id=0;
for(int j=1;j<=n;j++){
if(dis[j]<dis[id]&&flag[j]==false){
id=j;
}
}
flag[id]=true;
int siz=G[id].size();
for(int j=0;j<siz;j++){
int v=G[id][j].v,w=G[id][j].w;
dis[v]=min(dis[v],dis[id]+w);
}
}
return dis[T];
}
void Edge(int s,int t,int w){
G[s].push_back(edge(t,w));
// G[t].push_back(edge(s,w));如是无向加
}
int main(){
scanf("%d %d",&n,&m);
for(int i=1;i<=m;i++){
int s,t,w;
scanf("%d %d %d",&s,&t,&w);
Edge(s,t,w);
}
int S,T;
scanf("%d %d",&S,&T);
printf("%d",Dijkstra(S,T));
}
③ 堆优化代码
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;
const int M=100005;
int dis[M];
bool flag[M];
struct edge{
int v,w;
edge(){
}
edge(int V,int W){
v=V,w=W;
}
};
struct node{
int u,dis;
node(){
}
node(int U,int D){
u=U,dis=D;
}
friend bool operator < (node a,node b){return a.dis>b.dis;}
};
vector<edge> G[M];
priority_queue<node> Q;
int Dijkstra(int S,int T){
memset(dis,0x3f,sizeof(dis));
dis[S]=0,Q.push(node(S,0));
while(!Q.empty()){
int u=Q.top().u;
Q.pop();
if(flag[u]) continue;
flag[u]=true;
int v,w,siz=G[u].size();
for(int i=0;i<siz;i++){
v=G[u][i].v,w=G[u][i].w;
if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
Q.push(node(v,dis[v]));
}
}
}
return dis[T];
}
void Edge(int x,int y,int z){
G[x].push_back(edge(y,z));
}
int main(){
int n,m;
scanf("%d %d",&n,&m);
for(int i=1;i<=m;i++){
int s,t,w;
scanf("%d %d %d",&s,&t,&w);
Edge(s,t,w);
}
int S,T;
scanf("%d %d",&S,&T);
printf("%d",Dijkstra(S,T));
}
三、Bellman-Ford
对每条边执行更新,迭代N-1次,可以应用于负权图。
(1)初始化:dis[s] = 0, dis[i] = 0x3f3f3f3f(i≠s), pre[s]=0。
(2)
for (i = 1; i <= nodenum – 1; i ++)
for (j = 1; j <= edgenum – 1; j ++)
if (dis[u] + w[j] < dis[v]) { //relax
dis[v] = dis[u] + w[j];
pre[v] = u;
}
完整代码
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int M=100005;
int dis[M];
int S,T,n,m;
struct node{
int ui,vi,wi;
}edge[M];
bool BellmanFord(){
memset(dis,0x3f,sizeof(dis));
for(int i=1;i<=n-1;i++){
for(int j=1;j<=m;j++){
if(dis[edge[j].vi]>dis[edge[j].ui]+edge[j].wi){
dis[edge[j].vi]=dis[edge[j].ui]+edge[j].wi;
}
}
}
bool flag=true;
for(int i=1;i<=m;i++){
if(dis[edge[i].vi]>dis[edge[i].ui]+edge[i].wi){
flag=false;
break;
}
}
if(flag==true){
return true;
}
return false;
}
int main(){
scanf("%d %d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d %d %d",&edge[i].ui,&edge[i].vi,&edge[i].wi);
}
int S,T;
scanf("%d %d",&S,&T);
for(int i=1;i<=m;i++){
if(edge[i].ui==S){
dis[edge[i].vi]=edge[i].wi;
}
}
dis[S]=0;
if(BellmanFord()){
printf("%d",dis[T]);
return 0;
}
return 0;
}
四、Spfa
SPFA = 队列优化的Bellman-Ford算法,本质上还是迭代——每更新一次就考虑入队稀疏图上O(kN),稠密图上退化到O(N^2),可以应用于负权图
算法实现:
在Bellmanford算法中,有许多松弛是无效的。这给了我们很大的改进的空间。SPFA算法正是对Bellmanford算法的改进。它是由西南交通大学段丁凡1994提出的。它采用了队列和松弛技术。先将源点加入队列。然后从队列中取出一个点(此时该点为源点),对该点的邻接点进行松弛,如果该邻接点松弛成功且不在队列中,则把该点加入队列。如此循环往复,直到队列为空,则求出了最短路径。
判断有无负环:如果某个点进入队列的次数超过N次则存在负环 ( 存在负环则无最短路径,如果有负环则会无限松弛,而一个带n个点的图至多松弛n-1次)
完整代码
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;
const int M=100005;
int dis[M];
bool flag[M];
int S,T,n,m;
struct node{
int si,ti,wi;
node(){}
node(int X,int Y){
ti=X,wi=Y;
}
};
queue<int> q;
vector<node> vec[M];
void Edge(int x,int y,int z){
vec[x].push_back(node(y,z));
// vec[y].push_back(node(x,z));如是无向图加
}
int SPFA(){
memset(dis,0x3f,sizeof(dis));
memset(flag,false,sizeof(flag));
dis[S]=0,flag[S]=true,q.push(S);
while(!q.empty()){
int x=q.front();
q.pop();
flag[x]=false;
int siz=vec[x].size();
for(int i=0;i<siz;i++){
int Ti=vec[x][i].ti,Wi=vec[x][i].wi;
if(dis[Ti]>dis[x]+Wi){
dis[Ti]=dis[x]+Wi;
if(!flag[x]){
q.push(Ti),flag[Ti]=true;
}
}
}
}
return dis[T];
}
int main(){
memset(dis,0x3f,sizeof(dis));
scanf("%d %d",&n,&m);
for(int i=1;i<=m;i++){
int s,t,w;
scanf("%d %d %d",&s,&t,&w);
Edge(s,t,w);
}
int S,T;
scanf("%d %d",&S,&T);
int id1=SPFA();
printf("%d",id1);
}
总结
这次csp做的非常不好,其主要原因是在于思维没有打开,还有在考场上的策略有错误,而导致时间的不够以及题的思考深度不够。
但是这次也深刻的提醒与我,在平时的训练中更需要自己的思维训练。
所有代码纯手打,点个赞再走呗!