🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# 图的最短路径 ## Dijkstra 算法 适用于无负权图的单源最短路径问题,复杂度为 $$O(V^2+E)$$ ### 最短距离 #### 伪码 ```c++ // 数组 d[] 为源点 s 到达各点的最短路径长度 Dijkstra(G, d[], s){ 初始化; for(循环 n 次){ u=使 d[u] 最小的还未被访问的顶点标号; 记 u 已被访问; for(从 u 出发所有能到达的顶点 v){ if(v 未被访问 && 以 u 为中介点使 s 到顶点 v 的最短距离 d[v] 更优) 优化 d[v]; } } } ``` #### 邻接矩阵 ```c++ const int maxv=1000; const int inf=0x3fffffff; int n,G[maxv][maxv]; int d[maxv]; bool vis[maxv]={false}; void Dijkstra(int s){ // 初始化 fill(d,d+maxv,inf); d[s]=0; for(int i=0;i<n;i++){ // 查找使 d[u] 最小的还未被访问的顶点标号 u int u=-1,min=inf; for(int j=0;j<n;j++){ if(vis[j]==false && d[j]<min){ u=j; min=d[j]; } } // 找不到满足条件的 u if(u==-1) return; // 标记 u 已被访问; vis[u]=true; // 优化 vis 集到 u 所能到达的为被访问的顶点的距离 for(int v=0;v<n;v++){ if(vis[v]==false && G[u][v]!=inf && d[u]+G[u][v]<d[v]) d[v]=d[u]+G[u][v]; } } } ``` #### 邻接表 ```c++ const int maxv=1000; const int inf=0x3fffffff; int n; int d[maxv]; bool vis[maxv]={false}; struct Node{ int v,dis; }; vector<Node> Adj[maxv]; void Dijkstra(int s){ fill(d,d+maxv,inf); d[s]=0; for(int i=0;i<n;i++){ int u=-1,min=inf; for(int j=0;j<n;j++){ if(vis[j]==false && d[j]<min){ u=j; min=d[j]; } } if(u==-1) return; vis[u]=true; for(int j=0;j<Adj[u].size();j++){ int v=Adj[u][j].v; if(vis[v]==false && d[u]+Adj[u][j].dis<d[v]) d[v]=d[u]+Adj[u][j].dis; } } } ``` ### 最短路径 #### 伪码 在求最短距离伪码的 `优化 d[v];` 之后,加上 `令 v 的前驱为 u;` ```c++ // 数组 d[] 为源点 s 到达各点的最短路径长度 Dijkstra(G, d[], s){ 初始化; // 初始化令每个节点的前驱为自身 for(循环 n 次){ u=使 d[u] 最小的还未被访问的顶点标号; 记 u 已被访问; for(从 u 出发所有能到达的顶点 v){ if(v 未被访问 && 以 u 为中介点使 s 到顶点 v 的最短距离 d[v] 更优){ 优化 d[v]; 令 v 的前驱为 u; // 仅此处改动 } } } } ``` #### 邻接表 ```C #define MAXV 500 #define INF 999999 typedef int ElemType; typedef struct { int no; // 顶点编号 ElemType info; // 顶点其他信息 }VertexType; // 声明顶点类型 typedef struct { int edges[MAXV][MAXV]; // 邻接矩阵 int n, e; // 定点数和边数 VertexType vexs[MAXV]; // 存放顶点信息 }MGraph; // 声明图的邻接矩阵类型 // 初始化图的邻接矩阵 void CreateMat(MGraph &g, int A[][MAXV], int n) { // 由数组 A[n][n] 生成邻接矩阵 g int i, j; g.n = n; g.e = 0; for(i=0; i<g.n; i++){ for(j=0; j<g.n; j++){ g.edges[i][j] = A[i][j]; if(g.edges!=0) g.e++; } } } // 迪克斯特拉算法 void Dijkstra(MGragh g, int v) { int dist[MAXV]; // U 集合中的各节点到 v 的最短距离 int path[MAXV]; // 用于记录最短路径中每一节点的前一节点 int S[MAXV]; // 已访问集合 int mindist, i, j, u; // 将源点归入 S 集合,其余节点归入 U 集合 // 初始化 U 集合中各节点到 S 集合的距离 // 将与 S 集合相连的节点的路径中的前一节点标记为源节点 for(i=0; i<g.n; i++){ S[i] = 0; dist[i] = g.edges[v][i]; if(g.edges[v][i]<INF) path[i] = v; else path[i] = -1; } S[v] = 1; for(i=0; i<g.n; i++){ // 在 U 集合中找出距离 S 集合最近的节点 mindist = INF; for(j=0; j<g.n; j++){ if(!S[j] && dist[j]<mindist){ mindist = dist[j]; u = j; } } S[u] = 1; // 将 u 点加入到 S 中 // 修改 U 集合中各节点到 v 的最短距离 for(j=0; j<g.n; j++){ if(g.edges[u][j]<INF && dist[u]+g.edges[u][j]<dist[j]){ dist[j] = dist[u] + g.edges[u][j]; path[j] = u; } } } DispAllPath(g, dist, path, S, v); } void DispAllPath(MGraph g, int dist[], int path[], int S[], int v) { int i, j, k; int apath[MAXV], d; for(i=0; i<g.n; i++){ if(S[i] && i!=v){ printf("The shortest path from %d to %d is %d:", v, i, dist[i]); d = 0; apath[d] = i; k = path[i]; if(k==-1) printf("None\n"); else{ while(k!=v){ d++;apath[d] = path[k]; k = path[k]; } d++; apath[d] = v; printf("%d", apath[d]); for(j=d-1; j>=0; j--) printf("-->%d", apath[j]); printf("\n"); } } } } ``` ### 求所有最短路径 将 `pre[maxv]` 换成 `vector<int> pre[maxv]` ```c++ vector<int> pre[maxn]; void Dijkstra(int s){ fill(d,d+maxv,inf); d[s]=0; for(int i=0;i<n;i++){ int u=-1,min=inf; for(int j=0;j<n;j++){ if(vis[j]==false && d[j]<min){ min=d[j]; u=j; } } if(u==-1) return; vis[u]=true; for(int v=0;v<n;v++){ if(vis[v]==false && G[u][v]!=inf){ if(d[u]+G[u][v]<d[v]){ d[v]=d[u]+G[u][v]; pre[v].clear(); pre[v].push_bakc(u); }else if(d[u]+G[u][v]==d[v]){ pre[v].push_bakc(u); } } } } } ``` ### 第二标尺 第一标尺是路径长度,有时题目还会有第二标尺。 ## Bellman-Ford 算法与 SPFA 算法 复杂度为 $$O(VE)$$ ### 最短距离 ```c++ // 邻接表 struct Node{ int v, dis; }; vector<Node> Adj[maxn]; int n; int d[maxn]; bool Bellman(int s){ fill(d,d+maxn,inf); d[s]=0; // 求解数组 d for(int i=0;i<n-1;i++){ for(int u=0;u<n;u++){ for(int j=0;j<n;j++){ int v=Adj[u][j].v; int dis=Adj[u][j].dis; if(d[u]+dis<d[v]) d[v]=d[u]+dis; } } } // 判断负环 for(int u=0;u<n;u++){ for(int j=0;j<Adj[u].size();j++){ int v=Adj[u][j].v; int dis=Adj[u][j].dis; if(d[u]+dis<d[v]) return false; } } return true; } ``` ### 最短路径条数 因为该算法会反复累计已经算过的顶点,设置记录前驱的数组 `set<int> pre[maxv];` ,遇到一条与已有最短路径长度相同的路径时,重新计算最短路径条数。 ```c++ // ... else if(d[u]+dis==d[v]){ pre[v].insert(u); num[v]=0; set<int>:: iterator it; for(it=pre[v].begin();it!=pre[v].end();it++){ num[v]+=num[*it]; } } ``` ### SPFA (Shortest Path Faster Algorithm) 在 Bellman-Ford 算法,每次都要访问所有边,而实际上只有当某个顶点 u 的 d[u] 值改变时,从它出发的邻接点 v 的 d[v] 值才会改变。对此进行优化,得到 SPFA。 #### 伪码 ```c++ queue<int> q; 源点 s 入队; while(q非空){ for(u 的所有邻接边 u->v){ if(d[u]+dis<d[v]){ d[v]=d[u]+dis; if(v 当前不再队列){ v 入队; if(v 入队次数大于 n-1) 说明有可达负环, return; } } } } ``` #### 邻接表 ```c++ vector<Node> Adj[maxv]; int n, d[maxv],num[maxv]; // num 记录顶点的入队次数 bool inq[maxv]; bool SPFA(int s){ // 初始化 memset(inq,false,sizeof(inq)); memset(num,0,sizeof(num)); fill(d,d+maxv,inf); // 源点入队 queue<int> q; q.push(s); inq[s]=true; num[s]++; d[s]=0; // 主体 while(!q.empty()){ int u=q.front(); q.pop(); inq[u]=false; // 遍历 u 的所有邻接边 v for(int j=0;j<Adj[u].size();j++){ int v=Adj[u][j].v; int dis=Adj[u][j].dis; if(d[u]+dis<d[v]){ d[v]=d[u]+dis; if(!inq[v]){ q.push(v); inq[v]=true; num[v]++; if(num[v]>=n) return false; } } } } return true; } ``` ## Floyd 算法 适用于解决全源最短路径问题,时间复杂度为 $$O(V^3)$$ ### 伪码 ```c++ 枚举顶点 k in [1,n]; 以顶点为中介点,枚举所有顶点对 i,j in [1,n] if(dis[i][k]+dis[k][j]<dis[i][j]) dis[i][j]=dis[i][k]+dis[k][j]; ```