图论:最短路径
图论部分经常讨论的问题,寻找图中两个特定节点之间最短路径的长度。要解决这类问题需要根据实际情况选择合适的算法
Floyd算法,时间复杂度为O(n3),空间复杂度O(n2),配合邻接矩阵使用。当算法完成后,所有节点对之间的最短路径都会被确定,因此适用于全源最短路问题。由于时间复杂度的限制,一般不太能使用......主要的思想是:dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j])。示例代码如下
#include<bits/stdc++.h> using namespace std; int n,m,s; int ans[10005][10005]; int main() { scanf("%d%d%d",&n,&m,&s); for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ ans[i][j]=-1; } ans[i][i]=0; } int a,b,c; while(m--){ scanf("%d%d%d",&a,&b,&c); ans[a][b]=c; } for(int k=1;k<=n;k++){ for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ if(ans[i][k]==-1||ans[k][j]==-1){ continue; } if(ans[i][j]==-1||ans[i][j]>ans[i][k]+ans[k][j]){ ans[i][j]=ans[i][k]+ans[k][j]; } } } } for(int i=1;i<=n;i++){ if(i!=1){ printf(" "); } printf("%d",ans[s][i]); } printf("\n"); return 0; }
Dijkstra算法,时间复杂度O(n2),空间复杂度O(n)。当使用堆来替换算法中寻找最小权值边的过程,时间复杂度可以优化到O(nlgn)。可能比较适合使用邻接矩阵(邻接矩阵借助vector来记录)。与Floyd算法不同的是,它适用于求解从一个指定节点出发,到其他节点最短路径的问题,即单源最短路问题。需要注意的是不能处理负环。主要思想的话,将节点分为两堆,每次查询与新确定的节点相连的边,从中选出权值最小的一条边在下一回加入。首先是未经优化的代码:
#include<bits/stdc++.h> using namespace std; int n,m,s; struct edge { int next,cost; }; vector<edge> edges[100005]; bool mark[100005]; int dis[100005]; int main() { scanf("%d%d%d",&n,&m,&s); //memset(dis,0x7fffffff,n+1); int a,b,c; for(int i=1;i<=n;i++){ dis[i]=0x7fffffff; edges[i].clear(); } for(int i=1;i<=m;i++){ scanf("%d%d%d",&a,&b,&c); edge temp; temp.next=b; temp.cost=c; edges[a].push_back(temp); } dis[s]=0; mark[s]=true; int newP=s; for(int i=2;i<=n;i++){ for(int j=0;j<edges[newP].size();j++){ int next=edges[newP][j].next; if(mark[next]){ continue; } int cost=edges[newP][j].cost; if(dis[next]>dis[newP]+cost){ dis[next]=dis[newP]+cost; } } int minn=0x7fffffff; for(int j=1;j<=n;j++){ if(mark[j]){ continue; } if(dis[j]<minn){ minn=dis[j]; newP=j; } } mark[newP]=true; } for(int i=1;i<=n;i++){ if(i!=1){ printf(" "); } printf("%d",dis[i]); } printf("\n"); return 0; }
然后是经过堆优化的方案,复杂度降到O(n*logn),平时做题也一般是用这种:
#include<bits/stdc++.h> using namespace std; int n,m,s; struct edge { int next,cost; }; struct node { int index; int dis; bool operator <(const node &x) const { return x.dis<dis; } }; vector<edge> edges[100005]; bool mark[100005]; int dis[100005]; priority_queue<node> q; int main() { scanf("%d%d%d",&n,&m,&s); int a,b,c; for(int i=1;i<=n;i++){ dis[i]=0x7fffffff; edges[i].clear(); } for(int i=1;i<=m;i++){ scanf("%d%d%d",&a,&b,&c); edge temp; temp.next=b; temp.cost=c; edges[a].push_back(temp); } dis[s]=0; q.push((node){s,0}); while(!q.empty()){ node temp=q.top(); q.pop(); if(mark[temp.index]){ continue; } mark[temp.index]=true; for(int j=0;j<edges[temp.index].size();j++){ int next=edges[temp.index][j].next; if(mark[next]){ continue; } int cost=edges[temp.index][j].cost; if(dis[next]>dis[temp.index]+cost){ dis[next]=dis[temp.index]+cost; q.push((node){next,dis[next]}); } } } for(int i=1;i<=n;i++){ if(i!=1){ printf(" "); } printf("%d",dis[i]); } printf("\n"); return 0; }
最后是SPFA算法,主要是用来处理负环最短路问题。没有负权边绝对不要用SPFA!!!堆优化的dij就很香。SPFA是Bellman-Ford算法的队列实现,最坏情况下和Bellman-Ford算法复杂度一样,O(V*E)。其实代码实现和dij有相似之处,不同点在于对入队次数进行了统计以应对负权边。而且不能重复入队,队列一次只能有一个该点,但是可以修改dis。
bool spfa() { dis[s]=0; mark[s]=true; q.push(s); cnt[s]++; while(!que.empty()){ int now=que.front(); mark[now]=false; for(int i=0;i<edges[now].size();i++){ int next=edges[now][i].next; int cost=edges[now][i].cost; if(dis[next]>dis[now]+cost){ dis[next]=dis[now]+cost; if(!mark[next]){ mark[next]=true; q.push(next); cnt[next]++; if(cnt[next]>=n){ return true; } } } } } return false; }


浙公网安备 33010602011771号