图的路径:最短路-四种算法
先说前三种,主要解决的问题是:“求a点到b点的最短路”,解决方法是:用以下三种算法中的一种,求出a点到其他所有点的最短路,即可知道a点到b点的最短路。
前三种最短路算法是:Dijkstra(及优先队列优化),Bellman-Ford,SPFA(实际上就是BF的优化版)。
Dijkstra算法: (要求边权只能大于等于0)
未优化 O(N2):
贪心算法:每次找到当前距离起始点(a点)最短的点扩展更新,且该点不会再被更新;
数据结构:开一个二维数组cost[i][j]来存储各点之间的距离(邻接矩阵),INF表示无通路,x点到x点之间距离为0;用一维数组lowcost[i]表示最短路径,用vis[i]保存路径中某点是否已经走过。
Dij+邻接矩阵 模板如下:
const int INF=10e7; const int MAXN=1010; int k,minn; int cost[MAXN][MAXN]; int lowcost[MAXN]; bool vis[MAXN]; void dij(int n,int start) { for(int i=1;i<=n;i++) lowcost[i]=INF,vis[i]=0; lowcost[start]=0; for(int j=1;j<=n;j++) { k=-1,minn=INF; for(int i=1;i<=n;i++) { if(!vis[i]&&lowcost[i]<minn) {minn=lowcost[i];k=i;} } if(k==-1) break; //这里需要对k进行判定,是因为有可能在k未赋值的情况下使用了vis[k] vis[k]=1; for(int i=1;i<=n;i++) if(!vis[i]&&cost[k][i]>=0&&lowcost[k]+cost[k][i]<lowcost[i]) lowcost[i]=lowcost[k]+cost[k][i]; } }
优先队列优化 O(NlogN): //且对于稠密图,使用优先队列优化的Dijkstra算法更有效
将原先未优化版的第一个O(N)优化,用优先队列来维护,复杂度变为O(logN),故总复杂度为O(NlogN)。
Dij+邻接表+优先队列 模板如下:
const int MAXN=1010; const int INF=10e8; struct node { int v,val; node(int _v=0,int _val=0): v(_v),val(_val) {} //等价于node(int _v,int _val) {v=_v;val=_val;} bool operator < (const node &a) const { if(val==a.val) return v<a.v; else return val>a.val; } }; vector<node> E[MAXN]; bool vis[MAXN]; void Dijkstra(int lowcost[],int n,int start) { int len,u,v,cost; node qtemp; for(int i=1;i<=n;i++) lowcost[i]=INF;vis[i]=0; lowcost[start]=0; //用优先队列优化 priority_queue<node> que; que.push(node(start,0)); while(!que.empty()) //类似于BFS { qtemp=que.top();que.pop(); u=qtemp.v; if(vis[u]) continue; vis[u]=1;len=E[u].size(); for(int i=0;i<len;i++) { v=E[u][i].v;cost=E[u][i].cost; if(!vis[v]&&lowcost[v]>lowcost[u]+cost) { lowcost[v]=lowcost[u]+cost; que.push(node(v,lowcost[v])); } } } }
Bellman-Ford算法: (边权可以为负权,且可用于判断图是否存在负权回路)
原理是进行松弛操作,最多N-1次,复杂度O(NM)。(松弛一次的复杂度为O(M))
步骤:
1.初始化所有点,每个点保存一个值,表示从原点到此点的距离,INF表示两点之间不连通;
2.进行循环,下标从1到n-1(n为图中点的个数),复杂度O(N),在循环内部遍历所有的边,进行松弛,复杂度O(M);
3.跳出循环后,遍历途中所有的边(edge(u,v)),判断是否存在:
lowcost(v)>lowcost(u)+cost(u,v)的情况,若存在,则返回false,表示图中存在负权回路,无法求得最短路径。
BF+邻接表 模板如下:
const int MAXN=1010; const int INF=10e8; struct edge { int u,v,cost; edge(int _u=0,int _v=0,int _cost=0) u(_u),v(_v),cost(_cost) {} }; vector<edge> E; bool Bellman_Ford(int lowcost[],int n,int start) { //返回值用于判断是否有负环,false说明有负环,即无法求出最短路 bool ok; int u,v,cost; int len=E.size(); for(int i=1;i<=n;i++) lowcost[i]=INF; lowcost[start]=0; for(int i=0;i<n-1;i++) { ok=0; for(int j=0;j<len;j++) { u=E[j].u;v=E[j].v;cost=E[j].cost; if(lowcost[v]>lowcost[u]+cost) //进行松弛 { lowcost[v]=lowcost[u]+cost; ok=1; } } if(!ok) return true; } for(int j=0;j<len;j++) if(lowcost[E[j].v]>lowcost[E[j].u]+E[j].cost) return false; return true; } //加边操作 inline void add_edge(int u,int v,int c) { E.push_back(edge(u,v,c)); }
SPFA算法:
是优化后的BF算法,在进行松弛的时候,每次只对上一次改变过的点进行松弛。
SPFA+邻接表 模板如下:
const int MAXN=1010; const int INF=10e8; struct edge { int v,cost; edge(int _v=0,int _cost=0) v(_v),cost(_cost) {} }; vector<edge> E[MAXN]; bool vis[MAXN]; int couNode[MAXN]; bool SPFA(int lowcost[],int n,int start) { //返回值用于判断是否有负环,false说明有负环,即无法求出最短路 queue<int> que; int u,v,cost; int len=E.size(); for(int i=1;i<=n;i++) lowcost[i]=INF,vis[i]=0,couNode[i]=0; lowcost[start]=0;vis[start]=0;couNode[start]=1; que.push(start); for(!que.empty()) { u=que.front();que.pop(); vis[u]=0;len=E[u].size(); for(int i=0;i<len;i++) { v=E[i].v;cost=E[i].cost; if(lowcost[v]>lowcost[u]+cost) //进行松弛 { lowcost[v]=lowcost[u]+cost; if(!vis[v]) { vis[v]=1; ++couNode[v]; que.push(v); if(couNode[v]>n) return false; } } } } return true; } //加边操作 inline void add_edge(int u,int v,int c) { E[u].push_back(edge(v,c)); }
Floyd算法:
第四种比较特殊又好用的算法是folyd算法;
若要求图上任意两点之间的最短路(而不是给了确切的起点a),就用Folyd算法;此算法代码短,复杂度O(N3)用的是动态规划思想。
且Floyd算法也可用于判环,若lowcost[a][a]有值且不为INF,那么说明图中存在从a点到a点的一个环。
Floyd+邻接矩阵 模板如下:
const int INF=10e7; const int MAXN=1010; int lowcost[MAXN][MAXN]; //记录a点到b点之间的最短路 void floyd(int n) { for(int k=1;k<=n;k++) //千万注意k要在最外层循环,否则会出错 for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) lowcost[i][j]=min(lowcost[i][j],lowcost[i][k]+lowcost[k][j]); }