本文最后更新于:2024年1月7日 晚上
多段图最短路径问题
1
动态规划法
动态规划法将待求解问题分解成若干个相互重叠的子问题,每个子问题对应决策过程的一个阶段,一般来说,子问题的重叠关系表现在对给定问题求解的递推关系(称为动态规划函数)中,将子问题的解求解一次并填入表中,当需要再次求解此子问题时,可以通过查表获得该子问题的解,从而避免了大量重复计算。具体的动态规划法多种多样,但都具有相同的填表形式。一般来说,动态规划法的求解过程由以下三个阶段组成:
- 划分子问题:将原问题分解为若干个子问题,每个子问题对应一个决策阶段,并且子问题之间具有重叠关系。
- 确定动态规划函数:根据子问题之间的重叠关系找到子问题满足的递推关系式即动态规划函数,这是动态规划法的关键。
- 填写表格:设计表格,以自底向上的方式计算各个子问题的解并填表,实现动态规划过程。
上述动态规划过程可以求得问题的最优值即目标函数的极值,如果要求出具体的最优解,通常在动态规划过程中记录必要的信息,再根据最优决策序列构造最优解。
多段图最短路径问题
设图 \(G =(V,E)\)
是一个带权有向图,如果把顶点集合 V 划分成 k 个互不相交的子集 \(V_i(2≤k≤n,1≤i≤k)\),使得 E 中的任何一条边
<u,v>,必有 \(u∈V_i, v∈V_{i + m}(i<k,
1<i+m≤k)\),则称图 G 为多段图,称 s∈V1 为源点,t∈Vk
为终点。
多段图的最短路径问题为从源点到终点的最小代价路径,如下动图所示:
多段图是一个有向的无环图。求解从起始点到终止点的最短路径的长度,
首先看一下这个问题是否具有最优子结构的性质。对于每一点来说,从v0
到它的最短路径有两种可能,分别是从v0直接到该点或者是从最短的前驱节点开始到该节点。从这里可以看出有递归的性质,所以使用回溯的方法也是可以解决的。即从终点开始,依次向前找到最短的路径。由于递归本身所用的时间较长,并且在回溯的过程中存在重复的工作,所以使用动态规划更好。
最优子结构证明
适合采用动态规划方法的最优化问题中的两要素:
✓ 最优子结构
✓ 重叠子问题
一、 最优子结构
二、重叠子问题
- 适用于动态规划求解的最优化问题的第二个要素是子问题的空间要小,使用来解原问题的递归算法可反复解同样的子问题,而不总在产生新的子问题。
- 不同的子问题数是输入规模的一个多项式。
- 递归算法求解问题时,每次产生的子问题并不总是新问题,有些子问题被反复计算多次,该性质称为子问题的重叠性质
设s,s,s1,s2,……,sp,t是s到t的一条最短路径,且s到s1的路径已经求出,则问题转为s1到t的最短路径,因此s1,s2,……,sp,t构成一条最短路径,如果不是,则设s1,r1,r2,……,rp,t是一条从s1到t的最短路径,则s,s1,r1,r2,……,rp,t是一条从s到t的最短路径且比s,s1,s2,……,sp,t短,因此矛盾,所以多段图的最短路径问题满足最优性原理。
问题分析
程序实现
c
arc[N] [N]
:图的邻接矩阵 cost[N]
:一维数组
存储到每个顶点的最小开销。
path[N]
:用来保存每个顶点的前驱顶点,注意这个前驱结点是最短路径上的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| #include <stdio.h>
#define N 100 #define MAX 999999
int arc[N][N];
int CreateGraph() { int i, j, k; int weight; int vnum, arcnum;
printf("请输入顶点的个数: "); scanf("%d", &vnum); printf("请输入边的个数: "); scanf("%d", &arcnum); for (i = 1; i <= vnum; i++) { for (j = 1; j <= vnum; j++) { arc[i][j] = MAX; } arc[i][i] = 0; }
printf("请输入边的两个顶点和权值: \n"); for (k = 0; k < arcnum; k++) { scanf("%d %d %d", &i, &j, &weight); arc[i][j] = weight; }
return vnum; }
void BackPath(int n) { int i, j; int cost[N], path[N];
for (i = 1; i <= n; i++) { cost[i] = arc[1][i]; path[i] = 1; }
for (i = 2; i <= n; i++) { for (j = 1; j <= n; j++) { if (cost[i] + arc[i][j] < cost[j]) { cost[j] = arc[i][j] + cost[i]; path[j] = i; } } } for (i = 1; i <= n; i++) { printf("从顶点1到顶点%d的最小开销为:%d,路径为:", i, cost[i]); j = i; while (j != 1) { printf("%d <- ", j); j = path[j]; } printf("1\n"); } }
int main() { int vnum = CreateGraph(); BackPath(vnum); return 0; }
|
c++
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
| #include<iostream> #include<stdio.h> #define MAXV 11 using namespace std;
typedef struct{ int edges[MAXV][MAXV]; int n; } MGraph;
MGraph topography; int path[MAXV] = {}; int min_cost[MAXV] = {};
MGraph CreateMGraph(int num) { int n; int point1, point2; int value; for(int i = 1; i <= num; i++){ for(int j = 1; j <= num; j++){ topography.edges[i][j] = 0; } } cout << "请输入边数:"; cin >> n; for (int i = 0; i < n; i++){ cin >> point1 >> point2 >> value; topography.edges[point1][point2] = value; } cout << "\n建立的邻接矩阵为:" << endl; for(int i = 1; i <= num; i++){ for(int j = 1; j <= num; j++){ printf("%2d ",topography.edges[i][j]); } cout << endl; } cout << endl; topography.n = num; return topography; }
int main(){ int cities_num = 0; int a_cost; int pre; cout << "城市数量为:"; cin >> cities_num; topography = CreateMGraph(cities_num); min_cost[1] = 0; for(int i = 2; i <= topography.n; i++) { min_cost[i] = 99999; } for(int i = 2; i <= cities_num; i++){ for(int j = 1; j < i; j++){ if(topography.edges[j][i] != 0){ a_cost = min_cost[j] + topography.edges[j][i]; if(a_cost < min_cost[i]){ min_cost[i] = a_cost; path[i] = j; } } } } for(int i = 1; i <= cities_num; i++){ cout << "到顶点" << i << "的最小开销为:" << min_cost[i] << ",路径:" << i; pre = i; while(path[pre]){ cout << "<-" << path[pre]; pre = path[pre]; } cout << endl; } return 0; }
|
测试样例
输入数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 10 18 1 2 4 1 3 2 1 4 3 2 5 9 2 6 8 3 5 6 3 6 7 3 7 8 4 6 4 4 7 7 5 8 5 5 9 6 6 8 8 6 9 6 7 8 6 7 9 5 8 10 7 9 10 3
|
输出数据
算法分析
算法的时间复杂度主要由两部分组成:
第一部分是依次计算从源点到各个顶点的最短路径长度,由两层嵌套的循环组成,外层循环执行
n-1
次,内层循环对所有入边进行计算,并且在所有循环中,每条入边只计算一次。假定图的边数为
m,则时间性能是 O(m)。
第二部分是输出最短路径经过的顶点,设多段图划分为 k 段,其时间性能是
O(k)。
综上所述,时间复杂度为 O(m+k)。
参考:
动态规划法解多段图最短路径问题
- 乌漆WhiteMoon - 博客园 (cnblogs.com)
【动态规划】多段图最短路径(动图演示)-阿里云开发者社区
(aliyun.com)