Output
对于每组数据,请在一行里输出最短需要行走的距离。如果不存在从S到T的路线,就输出-1.
Sample Input
3 3
0 1 1
0 2 3
1 2 1
0 2
3 1
0 1 1
1 2
Sample Output
2
-1
floyd 暴力算法,核心代码三个for
#include
using namespace std;
const int inf=0x3f3f3f3f;
const int N=205;
int arr[N][N];
int n,m,st,ed;
inline void init()
{
for(int i=0;i>n>>m)
{
init();//初始化
for(int i=0;i>a>>b>>c;
if(arr[a][b]>c)arr[a][b]=arr[b][a]=c;//因为是双向,所以尽量取最小
}
floyd();
cin>>st>>ed;
cout<<(arr[st][ed]==inf?-1:arr[st][ed])<
dijsktra算法
用于无负边权的 单元 最短路径问题。
#include
using namespace std;
const int inf=0x3f3f3f3f;
const int N=205;
int arr[N][N];
int dis[N];//存储某个点到各个点的最短路径
bool vis[N];//不能成环,所以判断某个点是否走过
int n,m,st,ed;
inline void init()
{
for(int i=0;i>n>>m)
{
init();
for(int i=0;i>a>>b>>c;
if(arr[a][b]>c)arr[a][b]=arr[b][a]=c;
}
cin>>st>>ed;
for(int i=0;i
dijkstra+ heap算法
时间复杂度o((m+n)logn)级别小于n^2。利用优先队列自动排序,寻找最短边。这里有两种写法,一种用二维数组存储,另一种用邻接表存储。邻接表适用于边特别多的情况。
#include
using namespace std;
const int inf=0x3f3f3f3f;
const int N=205;
typedef pairPII;
int arr[N][N];
int dis[N];
bool vis[N];
int n,m,st,ed;
inline void init()
{
memset(dis,inf,sizeof dis);
memset(vis,false,sizeof vis);
for(int i=0;i,greater > q;
q.push({0,st});
while(q.size())
{
PII now=q.top();q.pop();//greater小顶堆自动排序队首为最小,less队首最大大顶堆。用这个来找最短边。
int vi=now.second;//最短边的另一端端点
if(vis[vi])continue;
vis[vi]=true;
for(int i=0;idis[vi]+arr[vi][i])//比较通过最短边是否为最短路径,更新到每个点的最短路径
{
dis[i]=dis[vi]+arr[vi][i];
q.push({dis[i],i});
}
}
}
}
int main()
{
int a,b,c;
while(cin>>n>>m)
{
init();//初始化应该在输入之前,否则数据没输入。
for(int i=0;i>a>>b>>c;
arr[a][b]=arr[b][a]=min(arr[a][b],c);
}
cin>>st>>ed;
dijkstra();
cout<<(dis[ed]==inf?-1:dis[ed])<
#include
//邻接表写法。存在大量边的情况下适合用邻接表
#define PUSH(x,y,z) G[x].push_back({y,z});
using namespace std;
const int inf=0x3f3f3f3f;
const int N=205;
typedef pair P;
int n,m,st,ed;
vector G[N];
int dis[N];
bool vis[N];
inline void init()
{
memset(dis,inf,sizeof dis);
memset(vis,false,sizeof vis);
for(int i=0;i,greater > q;
q.push({0,st});
while(q.size())
{
P now=q.top();
q.pop();//选最短边,找到最短边的另一个端点
int u=now.second;
if(vis[u])continue;
vis[u]=true;
for(int i=0;idis[u]+cost)//遍历更新,通过最短边到点G[u][i].first的最短距离。检测vis[v]是否走过,因为要更新通过u到各个点,防止成环。
{
dis[v]=dis[u]+cost;
q.push({dis[v],v});//仅c++11以上版本编译器支持。建议写成make_pair(int,int),make_pair作用是把参数变成pair类型,兼容所有版本,写法:q.push(make_pair);
}
}
}
}
int main()
{
int a,b,c;
while(cin>>n>>m)
{
init();
for(int i=0;i>a>>b>>c;
PUSH(a,b,c);//不用判断G[a][b].second>c因为,首先第一次输入用不上,其次如果出现重复数据,在更新的时候不影响更新他的最小值。
PUSH(b,a,c);
}
cin>>st>>ed;
dijkstra();
/*for(int i=0;i
bellman-flod算法
dijkstra无法解决负权问题,此算法可以,核心代码4行,循环n - 1次。
如果存在负边权,则无最短路径。
如何保证不成环,因为总共对每条边最多做n - 1次松弛,每次松弛只有两种可能情况(1)利用未用过的较短的边松弛(2)利用负边松弛,且只要用负边权,下次还会用用过的负边权。所以松弛n - 1次,最多利用n - 1条边,总共n个点所以不会成环。
#include
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 1e3 + 5;
int n,m,st,ed;
int pro[N],dis[N],u[N],v[N],w[N];
void Bellman_Ford()
{
for( int k = 0;k < n - 1; ++ k )
{
for( int i = 0;i < n; ++ i )pro[i] = dis[i];
for( int i = 0;i < m; ++ i )
{
dis[v[i]] = min(dis[v[i]],dis[u[i]] + w[i]);
dis[u[i]] = min(dis[u[i]],dis[v[i]] + w[i]);//无向图需要反向更新一次
}
bool check = false;
for( int i = 0;i < n; ++ i )
{
if(pro[i] != dis[i])
{
check = true;
break;
}
}
if(check)continue;
else break;
}
bool check = false;
for( int i = 0;i < m; ++ i )
{
if(dis[v[i]] > dis[u[i]] + w[i])
{
check = true;
break;
}
}
if(check)cout << "-1" << endl;
}
int main()
{
while(cin >> n >> m)
{
for( int i = 0;i < m; ++ i )cin >> u[i] >> v[i] >> w[i];
memset(dis,INF,sizeof dis);
cin >> st >> ed;
dis[st] = 0;
Bellman_Ford();
cout << ( dis[ed] == INF ? -1 : dis[ed]) << endl;
}
return 0;
}
Bellman-flod 算法 + 队列优化 (spfa算法)
在Bellman-flod 算法中可以发现,很多松弛是无法进行的,只有当某一条边松弛之后,他的邻边才有可能松弛。
每次成功松弛的点入队,每次循环用已经松弛过的边松弛其他的边。
判断有无负环:如果某个点进入队列的次数超过一定次数则存在负环(在一次循环中可能会更新多次,但只入队一次,更新次数不能决定是否成环,因为一条边可以同时由许多其他边更新,一旦其他边更新,对于当前要检测是否成环的边的更新次数会超过 n - 1,入队或者出队次数才能代表该边的松弛情况,当它入队超过n - 1时,表明成环)。也可以利用Bellman-flod中检测负边权的方法。
期望的时间复杂度O(kE), 其中k为所有顶点进队的平均次数,可以证明k一般小于等于2。
SPFA 在形式上和宽度优先搜索(bfs)非常类似,不同的是宽度优先搜索中一个点出了队列就不可能重新进入队列,但是SPFA中一个点可能在出队列之后再次被放入队列,也就是一个点改进过其它的点之后,过了一段时间可能本身被改进,于是再次用来改进其它的点,这样反复迭代下去。
SPFA算法有两个优化算法 SLF 和 LLL。
-
SLF:Small Label First 策略,设要加入的节点是j,队首元素为i,若dist(j)
-
LLL:Large Label Last 策略,设队首元素为i,队列中所有dist值的平均值为x,若dist(i)>x则将i插入到队尾,查找下一元素,直到找到某一i使得dist(i)<=x,则将i出对进行松弛操作。
-
引用网上资料,SLF 可使速度提高 15 ~ 20%;SLF + LLL 可提高约 50%。 在实际的应用中SPFA的算法时间效率不是很稳定,为了避免最坏情况的出现,通常使用效率更加稳定的Dijkstra算法。
#include
#define PUSH(x,y,z) G[x].push(make_pair(y,z))
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 1e3 + 5;
typedef pair P;
int n,m,st,ed;
int dis[N],arr[N][N],check[N];
bool vis[N];
inline void init()
{
memset(vis,false,sizeof vis);
memset(dis,INF,sizeof dis);
memset(check,0,sizeof check);
for( int i = 0;i < n; ++ i )
{
for( int j = i + 1;j < n; ++ j )
{
arr[i][j] = arr[j][i] = INF;
}
arr[i][i] = 0;
}
}
void spfa()
{
queue q;
vis[st] = true;
dis[st] = 0;
q.push(st);
while(q.size())
{
int now = q.front();
q.pop();
vis[now] = false;
for( int i = 0;i < n; ++ i )
{
if(dis[i] > dis[now] + arr[now][i])
{
dis[i] = dis[now] + arr[now][i];
if(!vis[i])//把松弛过但不在队列中的边推进去,进而松弛其他边
{
check[i] ++;
if(check[i] > n - 1 )
{
//cout << i << endl;
dis[ed] = INF;
break;
}
vis[i] = true;
q.push(i);
}
}
}
}
}
int main()
{
int a,b,c;
while(cin >> n >> m)
{
init();
for( int i = 0;i < m; ++ i )
{
cin >> a >> b >> c;
arr[a][b] = arr[b][a] = min(arr[a][b],c);
}
cin >> st >> ed;
spfa();
cout << (dis[ed] == INF ? -1 : dis[ed]) << endl;
}
return 0;
}
//邻接表写法
#include
#define PUSH(x,y,z) G[x].push_back(make_pair(y,z))
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 1e3 + 5;
typedef pair P;
int n,m,st,ed,dis[N],check[N];
bool vis[N];
vector G[N];
inline void init()
{
memset(dis,INF,sizeof dis);
memset(vis,false,sizeof vis);
memset(check,0,sizeof check);
for( int i = 0;i < N; ++ i )G[i].clear();
}
void spfa()
{
queue q;
q.push(st);
dis[st] = 0;
while(q.size())
{
int now = q.front();
q.pop();
vis[now] = false;//这里不能丢,如果vis[now]更新了,需要再次放入队列。
for( int i = 0;i < G[now].size(); ++ i )
{
int v = G[now][i].first;
if(dis[v] > dis[now] + G[now][i].second)
{
dis[v] = dis[now] + G[now][i].second;
if(!vis[v])
{
vis[v] = true;
q.push(v);
}
}
}
}
}
int main()
{
int a,b,c;
while(cin >> n >> m)
{
init();
for( int i = 0;i < m; ++ i )
{
cin >> a >> b >> c;
PUSH(a,b,c);
PUSH(b,a,c);
}
cin >> st >> ed;
spfa();
cout << (dis[ed] == INF ? -1 : dis[ed]) << endl;
}
return 0;
}
前向星和链式前向星
前向星
是一种特殊的边集数组,我们把边集数组中的每一条边按照起点从小到大排序,如果起点相同就按照终点从小到大排序,
并记录下以某个点为起点的所有边在数组中的起始位置和存储长度,那么前向星就构造好了.
用len[i]来记录所有以i为起点的边在数组中的存储长度.
用head[i]记录以i为边集在数组中的第一个存储位置.
最终可以达到快速访问指定起点的边集。缺点需要排序,快排nlogn.
链式前向星
事实上根本不需要排序,我们的目的是在一堆数组中直接访问指定起点的边集,我们可以牺牲一些空间保存同一类集合的下标即可,对于每一条边需要保存其起点、终点、长度等。(如果是开太多数组,牺牲太多,又和邻接矩阵没差,小声bb)
对比:链式前向星和领接表的遍历速度都不如邻接矩阵,但存储的边的数量都比邻接矩阵要多,极端情况下邻接表可能会卡vector。
存边
int head[N];//一般初始化为-1
struct node{
int next,to,w;
}edge[N];
写入
int cnt = 0;
void add(int u,int v,int w)
{
edge[cnt].to = v;
edge[cnt].w = w;
edge[cnt].next = head[u];//指向上一条同起点的边
head[u] = cnt ++;//更新head[u]为当前边
}
其中edge[i].to
表示第i条边的终点,edge[i].next
表示与第i条边同起点的下一条边的存储位置,edge[i].w
为边权值.
另外还有一个数组head[]
,它是用来表示以i为起点的第一条边存储的位置,实际上你会发现这里的第一条边存储的位置其实
在以i为起点的所有边的最后输入的那个编号.
整数快读模板
int rd()
{
int res = 0,f = 1;
char ch = getchar();
while(ch < '0' || ch > '9'){
if(ch == '-')f = -1;
ch = getchar();
}
while(ch >= '0' && ch <= '9'){
res = res * 10 + ch - '0';
ch = getchar();
}
return f * res;
}
链式前向星 模板题 洛谷P4479
#include
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 1e5 + 5;
const int M = 2e5 + 5;
typedef pair P;
struct node{
int next,to,w;
}edge[M];
int n,m,st,ed,cnt;
int head[N],dis[N];
bool vis[N];
inline void init()
{
cnt = 0;
memset(head,-1,sizeof head);
memset(dis,INF,sizeof dis);
memset(vis,false,sizeof vis);
}
int rd()
{
int res = 0,f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-')f = -1;
ch = getchar();
}
while(ch <= '9' && ch >= '0')
{
res = res * 10 + ch - '0';
ch =getchar();
}
return f * res;
}
inline void add(int u,int v,int w)
{
edge[cnt].to = v;
edge[cnt].w = w;
edge[cnt].next = head[u];
head[u] = cnt ++;
}
void dijkstra()
{
priority_queue,greater
> q;
dis[st] = 0;
q.push(make_pair(0,st));
while(q.size())
{
P now = q.top();
q.pop();
int u = now.second;
if(vis[u])continue;
vis[u] = true;
for( int i = head[u]; ~ i;i = edge[i].next)
{//因为head初始化为-1,当i = -1 时结束,因为-1存储形式为111……111 按位去反后为0,所以可以写作 ~ i
int v = edge[i].to;
if(!vis[v] && dis[v] > dis[u] + edge[i].w)
{
dis[v] = dis[u] + edge[i].w;
q.push(make_pair(dis[v],v));
}
}
}
}
int main()
{
int a,b,c;
init();
n = rd();
m = rd();
st = rd();
for( int i = 0;i < m; ++ i ){
a = rd();
b = rd();
c = rd();
add(a,b,c);
}
dijkstra();
for( int i = 1;i <= n; ++ i ){
printf("%d ",dis[i]);
}
puts("");
return 0;
}