目录
前言
OO最短路分三种算法,分别是Floyd,dijkstra,spfa。
不同于其他算法的事,最短路的三种算法全都要求掌握。
OOFloyd是多源最短路算法,时间复杂度O(n^3)。
OOdijkstra与spfa都是单源最短路算法,但dijkstra不支持负权,spfa容易被卡常数。另外再提一句,没有堆优化的spfa不如dijkstra。
问题基本类型
OO求解最短路经常会碰到两种题型,分别为单源最短路和多源最短路求解。
OO在某些情况下可能会碰到类似于给定m个特定的点,求经过这些节点的最短路(径)。更有甚者,还会给出一些特定的顺序,此处就不提这么多了。但是具体选择Floyd或者dijkstra,主要还是要看图的大小。
算法解析
Floyd
OO该算法虽然不怎么用,但也是一个比较有用的算法。有关于Floyd的题目也可以非常难其实也就是省选难度啦,比如说 POI 2012 FES-Festival。但是在这里作为一个栗子,做一做典型的 寻宝之路 就好了。以下是鄙人的代码
OO
#include<bits/stdc++.h>
using namespace std;
int n,m,ans=0,d[121][121],dis[121][121],way[10050];
int main () {
cin >> n >> m;
memset(dis,0x1,sizeof dis);
for (int i=1; i <= m; i++) cin >> way[i];
for (int i=1; i <= n; i++)
for (int j=1; j <= n; j++) cin >> d[i][j],dis[i][j]=d[i][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]=dis[i][j]>dis[i][k]+dis[k][j]?dis[i][k]+dis[k][j]:dis[i][j];
for (int i=1; i < m; i++) ans+=dis[way[i]][way[i+1]];
cout << ans;
}
OO
OO
OO据说是可以Ac的,有 网站 为证。
OO
Dijkstra
OO
定义
Dijkstra(迪杰斯特拉)算法是典型的单源最短路径算法,用于计算一个节点到其他所有节点的最短路径。主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。Dijkstra算法是很有代表性的最短路径算法,在很多专业课程中都作为基本内容有详细的介绍,如数据结构,图论,运筹学等等。Dijkstra一般的表述通常有两种方式,一种用永久和临时标号方式,一种是用OPEN, CLOSE表的方式,这里均采用永久和临时标号的方式。注意该算法要求图中不存在负权边。
运行原理
Dijkstra算法在运行时,首先要定义起点s,也就是源。初始化将d[i]赋值为∞,d[s]=0。每一次向外扩展时,询问所有未标记点,找出源路径最短的点并标记,同时从这个点出发更新所有相邻的未标记点。进行n-1次循环以使得计算出所有点的最短源路径。
OO
模板题code
OO此处讲的是经典模板弱化版,众所周知,没有优化想打出ac的标准版是不可能的
OO这是鄙人的代码
#include <bits/stdc++.h>
using namespace std;
int head[500050],to[500050],nxt[500050],val[500050],
cnt=0,vis[10050],dis[10050];
void add(int x,int y,int z) {
nxt[++cnt]=head[x];
head[x]=cnt;
to[cnt]=y;
val[cnt]=z;
}
int main () {
int n,m,s,x,y,v;
scanf("%d%d%d",&n,&m,&s);
for (int i=1; i <= m; i++) scanf("%d%d%d",&x,&y,&v),add(x,y,v);
for (int i=1; i <= n; i++) dis[i]=2147483647;
dis[s]=0;
for (int i=1; i <= n; i++) {
int minn=2147483647,t=-1;
for (int j=1; j <= n; j++)
if (!vis[j]&&dis[j]<minn) {
minn=dis[j];
t=j;
}
if(t == -1) break;
vis[t]=1;
for (int j=head[t];j;j=nxt[j]) {
if(!vis[to[j]]&&dis[to[j]]>val[j]+dis[t])
dis[to[j]]=val[j]+dis[t];
}
}for (int i=1; i <= n; i++)
printf("%d ",dis[i]);
}
堆优化code
OO我觉得这应该是找不到 模板题 的吧(雾)
OO这还是鄙人的代码
#include<stdio.h>
#include<queue>
#include<algorithm>
#include<iostream>
using namespace std;
int nex[400001];
int to[400001];
int val[400001];
int head[200001];
int f[200001];
int idx,x,y,z;
int inf=2147483647;
bool vis[200001];
struct Point
{
int number,dis;
inline bool operator < (const Point &a) const
{
return dis>a.dis;
}
};
priority_queue <Point> q;
void addedge(int a,int b,int c)
{
nex[++idx]=head[a];
head[a]=idx;
to[idx]=b;
val[idx]=c;
}
int n,m,s;
int main()
{
Point tmp;
scanf("%d%d%d",&n,&m,&s);
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&x,&y,&z);
addedge(x,y,z);
}
for(int i=1;i<=n;i++)
f[i]=inf;
tmp.number=s,tmp.dis=0;
f[tmp.number]=0;
q.push(tmp);
while(!q.empty())
{
int here=q.top().number;
q.pop();
if(vis[here])
continue;
vis[here]=1;
for(int j=head[here];j;j=nex[j])
if(f[to[j]]>val[j]+f[here]&&(!vis[to[j]]))
{
f[to[j]]=f[here]+val[j];
tmp.number=to[j];
tmp.dis=f[to[j]];
q.push(tmp);
}
}
for(int i=1;i<=n;i++)
printf("%d ",f[i]);
}
OOemmmm
OO问题不大
SPFA
OO
适用范围
当图中含有负边,这是dijkstra就没有用武之地,SPFA的作用就体现出来了。SPFA适用于存在负边权的图,包括求最短路或判负环(边权和为负数的环)。
OO
运行原理
建立一个队列,初始时队列里只有起始点,再建立一个二维记录起始点到所有点的最短路径(出事赋∞,每个点到自己的距离为0)。然后使用队列里的点作为起始点去刷新到其他点的最短路,若刷新成功且被刷新点不在队列中,则加入到队列最后。重复执行直到队列为空。
Ps:若某个点进入队列超过N次则代表该图存在负环。
鄙人不曾做过SPFA,并且也找不到SPFA的朴素算法
下面是我copied的堆优化和判负环
OO
堆优化code
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<queue>
#include<vector>
#include<climits>
using namespace std;
#define maxn 99999999
struct E{
int next;
int c;
int zhi;
}e[1005000];
inline int read()
{
char ls=getchar();for (;ls<'0'||ls>'9';ls=getchar());
int x=0;for (;ls>='0'&&ls<='9';ls=getchar()) x=x*10+ls-'0';
return x;
}
int last[100500];
int dis[100500];
int n,m;
int k;
int located[100500];
int heap[300500];
int heap_size;
inline void put(int d)
{
int now,next;
heap[++heap_size]=d;
now=heap_size;
located[d]=now;
while(now>1)
{
next=now>>1;
if(dis[heap[now]]>=dis[heap[next]])break;
located[d]=next;
located[heap[next]]=now;
swap(heap[now],heap[next]);
now=next;
}
return;
}
inline void change(int d)
{
int now,next;
now=located[d];
while(now>1)
{
next=now>>1;
if(dis[heap[now]]>=dis[heap[next]])break;
located[d]=next;
located[heap[next]]=now;
swap(heap[now],heap[next]);
now=next;
}
return;
}
inline int get()
{
int now,next,res;
res=heap[1];
heap[1]=heap[heap_size--];
now=1;
located[heap[1]]=1;
located[res]=0;
while(now*2<=heap_size)
{
next=now*2;
if(next<heap_size&&dis[heap[next+1]]<dis[heap[next]])++next;
if(dis[heap[now]]<=dis[heap[next]])break;
located[heap[now]]=next;
located[heap[next]]=now;
swap(heap[next],heap[now]);
now=next;
}
return res;
}
void spfa(int s)
{
dis[s]=0;put(s);
while(heap_size!=0)
{
int x;
x=get();
for(int j=last[x];j;j=e[j].zhi)
{
int y=e[j].next;
int c=e[j].c;
if(dis[x]+c<dis[y])
{
dis[y]=dis[x]+c;
if(located[y]==0)put(y);
else change(y);
}
}
}
return;
}
int main()
{
n=read(),m=read();
for(int i=1;i<=m;i++)
{
int a1,b1,c1;
a1=read();b1=read();c1=read();
e[++k].next=b1;
e[k].c=c1;
e[k].zhi=last[a1];
last[a1]=k;
e[++k].next=a1;
e[k].c=c1;
e[k].zhi=last[b1];
last[b1]=k;
}
for(int i=1;i<=n;i++)
dis[i]=maxn;
spfa(1);
cout<<dis[n]<<endl;
return 0;
}
OO
判负环code
来自于负环(模板)
//7777777
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<math.h>
#include<cstring>
#include<string>
#include<queue>
#include<utility>
#include<vector>
#define lson l , m , rt << 1
#define rson m+1 , r , rt << 1 | 1
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long ll;
const double pi = 3.1415926535;
const double eps = 1e-6;
const int MXN = 1e5 + 7;
const int MXM = 1e6 + 7;
const int MX = 1e4 + 7;
const int maxbit = 18;
const double val = pi/180.0;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
struct Edge
{
int to,w,next;
}e[MX << 1];
int cnt,n,m;
int head[MX];
inline void add(int u,int v,int w)
{
e[++cnt].to = v;
e[cnt].w = w;
e[cnt].next = head[u];
head[u] = cnt;
}
bool SPFA(int S)
{
int num[MX] = {0};
int vis[MX] = {0};
int d[MX];
queue<int>q;
mem(vis,0);
for(int i = 1;i <= n;++i) d[i] = INF;
d[S] = 0;
vis[S] = 1;
q.push(S);
while(!q.empty())
{
int u = q.front();
q.pop();
vis[u] = 0;
for(int i = head[u];i;i=e[i].next)
{
int v = e[i].to;
int w = e[i].w;
if(d[v] > d[u] + w){
d[v] = d[u] + w;
if(!vis[v])
{
q.push(v);
vis[v] = 1;
num[v]++;
}
if(num[v] > n) return true;//存在负环
}
}
}
return false;//不存在负环
}
void init()
{
mem(head,0);
mem(e,0);
cnt = 0;
}
int main()
{
int T;
cin >> T;
while(T--)
{
init();
cin >> n >> m;
while(m--)
{
int u,v,w;
cin >> u >> v >> w;
add(u,v,w);
//此题中要求 若出现负边则为单向边
if(w >= 0) add(v,u,w);
}
if(SPFA(1)) cout << "YE5" << endl;
else cout << "N0" << endl;
}
return 0;
}
OO