基本最短路算法集锦
算法总结:
①Dijkstra算法用的是贪心策略,每次都找当前最短路径的下一个最短距离点。所以不适合带有负权的情况。至于时间效率通过各种优化可以到达不同的程度。但是朴素的Dijkstra算法永远是最稳定的。
②Bellman-Ford算法是Dijkstra的一种变式,它摒弃了贪心的策略,但是每次都需要松弛所有的路径,所以也适合负权的情况。但是时间效率较低。有资料显示,Bellman-Ford算法也可以应用贪心策略,这属于高级技巧,这里不予考虑。
③Floyd算法用的是动态规划的思想,由于是不定源点,所以它需要对所有的情况进行松弛操作,最终获得所有的最短路径长度。由于需要遍历,所以它的时间效率比较低。但是遍历策略好的话,还是能很快得到想要的结果。
④SPFA算法是用FIFO队列优化的Bellman-Ford算法,所以支持负权的情况,由于已经将需要考虑的节点放入队列中,所以避免了很多不必要的松弛,时间效率也相对较高。由于队列中无法进行修改,所以时间效率相对不稳定。
⑤所有的队列优化其实都能转化为hash优化,这里不再考虑更进一步的优化。
一、Dijkstra算法:单源到所有节点的最短路算法。
算法要求:可以是无向图或有向图,所有边权均为正,最短路存在。如果图是五环图,则最长路也一定存在,但是如果是有环图,则最长路不一定存在。
算法思想:每次比较所有的从源点可以到达的路的长度,然后选出最短的一条放入源点集合,然后更新最短路径长度。这样,当所有的点都在源点集合中时,得到的长度就是最短路长度。
(1)使用邻接矩阵的稠密图普通Dijkstra算法
算法时间复杂度:O(n^2)
算法代码:
#include
#include
#include
using namespace std;
#define INF (1<<31)-1 //int最大值
#define MAXN 100 //最大节点数
int dist[MAXN],c[MAXN][MAXN];
//dist[i]存放从源点到i节点的最短距离,c数组用来表示图
int n,line; //n为节点数,line为边数
//迪杰斯特拉算法主算法模块
void Dijkstra(int v) //v代表源点
{
inti,j,temp,u; //temp用于暂时存放最短路径长度
boolvis[MAXN]; //判定重复标记数组
for(i=0;i { dist[i]=c[v][i]; vis[i]=0; } dist[v]=0; //源点到源点的距离设为0 vis[v]=1; //源点默认为已经访问 for(i=0;i { temp=INF; //初始化temp为最大值 u=v; //初始化最短节点为当前节点 for(j=0;j { u=j; //找出v距离最短的节点u temp=dist[j]; //更新最短距离 } if(temp==INF)return; //不存在其它节点了,返回 vis[u]=1; //标记该最短节点为已经访问 for(j=0;j dist[j]=dist[u]+c[u][v]; //更新最短距离 } } int main() { int p,q,len,i,j,origin; while(scanf(“%d%d”,&n,&line)) { if(!n&&!line) break; //如果图为空白图,则结束 for(i=0;i for(j=0;j c[i][j]=INF; //初始化节点间距离,默认为节点间全部不连通 while(line--) //输入这line条边的内容 { scanf(“%d%d%d”,&p,&q,&len); //p为边的起点,q为边的终点,len为边的长度 //下面是无向图的输入方式 if(len { c[p][q]=len; c[q][p]=len; } /*下面是有向图的输入方式 if(len c[p][q]=lem; 以上是有向图的输入方式*/ } for(i=0;i dist[i]=INF; //初始化最短距离数组为最大长度 scanf(“%d”,&origin); Dijkstra(origin); //最短路算法调用 printf(“%d\n”,dist[n]); } } (2)使用邻接表的稀疏图的普通Dijkstra算法 算法时间复杂度:O(m*log(n)) 算法代码:(代码以无向图为例,有向图部分以注释形式写在代码中) ###头文件以及宏定义部分省略### int first[MAXN]; //first[u]保存节点u的第一条边的编号 int u[MAXN],v[MAXN],w[MAXN]; //u[i]表示第i条边的一端端点,v[i]表示第i条边的另一端端点,w[i]表示第i条边的长度 int next[2*MAXN]; //next[i]表示第i条边的下一条边的编号【无向图】 //int next[MAXN]; next[i]表示第i条边的下一条边的编号。【有向图】 #########省略部分######### scanf(“%d%d”,&n,&m); for(i=0;i for(i=0;i<2*line;i++) //输入2*line条边,每次将边首插法插入链表表头(避免遍历链表)。 //如果是有向图,那么只有line条边,即i { scanf(“%d%d%d”,&u[i],&v[i],&w[i]); next[i]=first[u[i]]; first[u[i]]=i; //以下只有无向图才有 i++; u[i]=v[i-1]; v[i]=u[i-1]; next[i]=first[u[i]]; first[u[i]]=i; //以上只有无向图才有 } //对应的Dijkstra算法 void Dijkstra(int x) //v代表源点 { inti,j,temp,minu; //temp用于暂时存放最短路径长度 boolvis[MAXN]; //判定重复标记数组 for(i=first[x];i!=-1;i=next[i]) //dist数组与vis数组的初始化 { dist[v[i]]=w[i]; vis[v[i]]=0; } dist[x]=0; //源点到源点的距离设为0 vis[x]=1; //源点默认为已经访问 for(i=0;i { temp=INF; //初始化temp为最大值 minu=x; //初始化最短节点为当前节点 for(j=first[x];j!=-1;j=next[j]) { if((!vis[v[j]])&&dist[v[j]] { minu=j; //找出v距离最短的节点uu temp=dist[v[j]]; //更新最短距离 } } if(temp==INF)return; //不存在其它节点了,返回 vis[minu]=1; //标记该最短节点为已经访问 for(j=first[x];j!=-1;j=next[j])if((!vis[v[j]])&&dist[minu]+w[j] dist[v[j]]=dist[minu]+w[j]; //更新最短距离 } } (3)使用优先队列的Dijkstra优化算法 算法时间复杂度:O(n*lgn+ m)。一般情况下最快。 算法代码: #include #include ######其他头文件已经宏定义省略###### int first[MAXN]; //first[u]保存节点u的第一条边的编号 int u[MAXN],v[MAXN],w[MAXN]; //u[i]表示第i条边的一端端点,v[i]表示第i条边的另一端端点,w[i]表示第i条边的长度 int next[2*MAXN]; //next[i]表示第i条边的下一条边的编号【无向图】 //int next[MAXN]; next[i]表示第i条边的下一条边的编号。【有向图】 #########省略部分######### scanf(“%d%d”,&n,&m); for(i=0;i for(i=0;i<2*line;i++) //输入2*line条边,每次将边首插法插入链表表头(避免遍历链表)。 //如果是有向图,那么只有line条边,即i { scanf(“%d%d%d”,&u[i],&v[i],&w[i]); next[i]=first[u[i]]; first[u[i]]=i; //以下只有无向图才有 i++; u[i]=v[i-1]; v[i]=u[i-1]; next[i]=first[u[i]]; first[u[i]]=i; //以上只有无向图才有 } typedef pair priority_queue //对应的Dijkstra算法 int Dijkstra(int x) { inti,j; pq.push(make_pair(d[x],x); while(!pq.empty()) { piiminu=pq.top();pq.pop(); intx=minu.second; if(minu.first!=dist[x])continue; for(inti=first[x];i!=-1;i=next[i]) if(d[v[i]]>dist[x]+w[i]) { dist[v[i]]=dist[x]+w[i]; pq.push(make_pair(d[v[i]],v[i])); } } } 二、Bellman-Ford算法:单源到所有节点的最短最长路算法。 算法要求:可以使无向图或者有向图。边权可以存在负值。当然最短路不一定存在,当最短路存在时,可以求出最短路长度。如果图为有环图,则最短路不一定存在,最长路也不一定存在。如有负权则输出错误提示。也适合求解约束拆分系统的不等式问题。 算法思想:如果最短路存在,一定存在一个不含环的最短路。在边权可正可负的图中,环有零环、正环、负环3种。如果包含零环或正环,去掉以后路径不会变长;如果包含负环,则最短路不存在。可以通过n-1轮松弛操作得到。 (1)朴素的BF算法。 算法时间复杂度:O(mn) 算法代码: const int N =205; const int M =20005; const int MAXN = 1000000000 int dist[N]; //自定义边的结构体 struct edge{int u,v,w;}e[M]; //u,v分别是边的两端点,w为边长度 //初始化dist数组 void init(int vs,int s) { inti; for(i=0;i dist[i]=MAXN; dist[s]=0; return; } //松弛操作,成功返回true,失败返回false bool relax(int u,int v,int w) { if(dist[v]>dist[u]+w) { dist[v]=dist[u]+w; returntrue; } return false; } //BF主算法模块,返回false表示算法失败,图中存在负环。 bool bellmanFord(int es,int vs,int s) //es表示边数,vs表示点数,s表示起点 { inti,j; init(vs,s); boolflag; for(i=0;i { flag=false; for(j=0;j if(relax(e[j].u,e[j].v,e[j].w)) flag=true; return flag; } } int main() { intn,m,i; while(scanf(“%d%d”,&n,&m)!=EOF) { for(i=0;i { scanf(“%d%d%d”,&e[i].u,&e[i].v,&e[i].w); e[m+i].u=e[i].v; e[m+i].v=e[i].u; e[m+i].w=e[i].w; } if(bellmanFord(m<<1,n,1)) printf(“%d\n”,dist[n]); else printf(“No\n”); } return 0; } (2)使用FIFO队列的优化BF算法(使用邻接表) 算法时间复杂度:O(mn) 算法代码: #include #define INF (1<<31)-1 ######其他头文件以及宏定义省略###### int first[MAXN]; //first[u]保存节点u的第一条边的编号 int u[MAXN],v[MAXN],w[MAXN]; //u[i]表示第i条边的一端端点,v[i]表示第i条边的另一端端点,w[i]表示第i条边的长度 int next[2*MAXN]; //next[i]表示第i条边的下一条边的编号【无向图】 //int next[MAXN]; next[i]表示第i条边的下一条边的编号。【有向图】 #########省略部分######### queue bool inq[MAXN]; bool bellmanFord(int x) { int i,j; bool ans; for(i=0;i memset(inq,0,sizeof(inq)); //在队列中的标志 q.push(x); ans=false; while(!q.empty()) { int x=q.front();q.pop(); inq[x]=false; for(i=first[x];i!=-1;i=next[i]) if(dist[v[e]]>dist[x]+w[e]) { dist[v[e]]=dist[x]+w[e]; ans=true; if(!inq[v[e]]) { inq[v[e]]=true; q.push(v[e]); } } } return ans; } 三、Floyd-Warshall算法:任意两点之间的最短路 算法要求:无特殊要求。 算法思想:动态规划。 时间复杂度:O(n^3) 算法代码: #define MAXN 100 #define INF (1<<31)-1 int n,m,p,q; int f[MAXN+10][MAXN+10]; void Floyd() { inti,j,k; for(k=0;k { for(i=0;i { for(j=0;j { if(f[i][k]+f[k][j] f[i][j]=f[i][k]+f[k][j]; } } } if(f[0][n-1]==INF) printf(“0\n”); else printf(“%d\n”,f[0][n-1]); } int main() { inta,b,c,i; while(~scanf(“%d%d”,&n,&m)) { if(!n&&!m) break; for(p=0;p for(q=0;q f[p][q]=INF; for(i=0;i { scanf(“%d%d%d”,&a,&b,&c); f[a][b]=c; f[b][a]=c; } floyd(); } return 0; } 四、SPFA算法:单源点最短路的高效实用算法 算法要求:无特殊要求 算法思想:用FIFO队列优化的BF算法。 算法时间复杂度:O(k|E|),k为常数,一般k<=2 算法代码: #include #######省略部分###### #define INF (1<<31)-1 #define N 1010 int dist[N],n,m; int edge[N][N]; bool vis[N]; void spfa(int s) { inti,u; memset(vis,false,sizeof(vis)); for(i=0;i queue q.push(s); vis[s]=true; dist[s]=0; while(!q.empty()) { u=q.front(); q.pop(); vis[u]=false; for(i=0;i { if(dist[i]>dist[u]+edge[u][i]) dist[i]=dist[u]+edge[u][i]; if(!vis[i]) { vis[i]=true; q.push(i); } } } } int main() { inti,j,a,b,c,origin; while(scanf(“%d%d”,&n,&m)!=EOF&&(n||m)) { for(i=0;i for(j=0;j
{ edge[i][j]=INF; edge[j][i]=INF; } }, for(i=0;i { scanf(“%d%d%d”,&a,&b,&c); if(edge[a][b]>c) { edge[a][b]=c; edge[b][a]=c; } scanf(“%d”,&origin); spfa(origin); printf(“%d\n”,dist[n]); } return 0; }