【问题】设图 G = (V,E) 是一个带权有向图,如果把顶点集合 V 划分成 k 个互不相交的子集(2 ≤ k ≤ n,1 ≤ i ≤ k),使得 E 中的任何一条边 <u,v>,必有 u V,v ( 1 ≤ i < k,1 < i+m ≤ k),则称图 G 为多段图,称 s 为源点,t 为终点。多段图的最短路径问题 (multi-segment graph shortest path problem) 求从源点到终点的最小代价路径。
【想法】由于多段图将顶点划分为 k 个互不相交的子集,所以,可以将多段图划分为 k 段,每一段包含顶点的一个子集,根据多段图的定义,,每个子集中的顶点互不邻接。不失一般性,将多段图的顶点按照段的顺序进行编号,同一段内顶点的顺序无关紧要。假设图中的顶点个数为 n,则源点 s 的编号为 0,终点t的编号为 n-1,并且对图中的任何一条边<u,v>,顶点 u 的编号小于顶点 v 的编号。下图(一个多段图)所示是一个含有10个顶点的多段图。
首先证明多段图的最短路径问题满足最优性原理。设 是从 s 到 t 的一条最短路径,从源点 s 开始,设从 s 到下一段的顶点 已经求出,则问题转化为求从 s 到 t 的最短路径,显然 一定构成一条从 到 t 的最短路径,如若不然,设 是一条从 到 t 的最短路径,则 将是一条从 s 到 t 的路径且比 的路径长度要短,从而导致矛盾。所以,多段图的最短路径问题满足最优性原理。
如何定义子问题呢?设 表示多段图的有向边 < u,v > 上的权值,将从源点 s 到终点 t 的最短路径长度记为 d(s,t),考虑原问题的部分解 d(s,t),显然有下式成立:
对于上图(一个多段图)所示多段图,动态规划法求解最短路径的填表过程如下图(多段图最短路径问题的填表过程)所示:
具体过程如下(括号中给出了决策产生的状态转移)。
首先求解初始子问题,可直接获得:
再求解下一阶段的子问题,有:
再求解下一阶段的子问题,有:
直到最后一个阶段有:
再将状态进行回溯,得到最短路径为0→3→5→8→9,最短路径长度为16。
【算法】设数组 cost[ n ] 存储最短路径长度,cost[ j ] 表示从源点 s 到顶点 j 的最短路径长度,数组 path[ n ] 记录状态转移,path[ j ] 表示从源点 s 到顶点 j 的路径上顶点 j 的前一个顶点,动态规划法求解多段图的最短路径问题的算法如下。
算法:多段图的最短路径问题
输入:多段图的代价矩阵
输出:最短路径长度及路径 c[ n ][ n ]
1.循环变量 j 从1 ~ n-1 重复下述操作,执行填表工作:
1.1 考察顶点 j 的所有入边,对于边 < i,j >∈ E,执行下述操作:
1.1.1 cost[ j ] = min{ cost[ i ] + c[ i ][ j ] };
1.1.2 path[ j ] = 使 cost[ i ] + c[ i ][ j ]最小的 i;
1.2 j++;
2.输出最短路径长度 cost[ n - 1 ];
3.循环变量 i = path[ n - 1],循环直到path[ i - ] = 0,输出最短路径经过的顶点:
3.1 输出path[ i ];
3.2 i = path[ i ]。
【算法分析】算法多段图的最短路径问题主要由两部分组成:第一部分是依次计算从源点到各个顶点的最短路径长度,由两层嵌套的循环组成,外层循环执行 n - 1 次,内层循环对所有入边进行计算,并且在所有循环中,每条人边只计算一次。假定图的边数为 m,则时间性能是 第二部分是输出最短路径经过的顶点,设多段图划分为 k 段,其时间性能是。所以,算法多段图的最短路径问题的时间复杂性为。
【算法实现】设多段图中 n 个顶点的编号为{0,1,…,n-1},数组 arc[ n ][ n ]存储边上的权值,为避免传递参数,将 arc[ n ][ n ]设为全局变量。设数组 cost[ n ] 存储最短路径长度,数组 path[ n ]记录状态转移。设函数 CreatGraph 建立多段图的代价矩阵并返回图的顶点个数,函数 BackPath 求得多段图的最短路径并返回其长度,算法用JAVA语言描述如下。
import java.util.Scanner;
public class creatGraph {
int N = 20;
int MAX = 1000;
int [][]arc = new int[N][N];
public static void main(String[] args) {
creatGraph creatgraph =new creatGraph();
int n = creatgraph.creatGraph( );
int pathLen = creatgraph.Backpath(n);
System.out.println("最短路径的长度是:"+pathLen);
}
int creatGraph()
{
int i, j, k;
int weight;
int vnum,arcnum;
System.out.println("请输入顶点的个数和边的个数:");
Scanner sc = new Scanner(System.in);
vnum=sc.nextInt();
arcnum =sc.nextInt();
for (i = 0; i < vnum; i++) //初始化图的代价矩阵
for (j = 0; j < vnum; j++)
arc[i][j] = MAX;
for (k = 0; k < arcnum; k++) //建立图的代价矩阵
{
System.out.println("请输入边的两个顶点和权值:");
i= sc.nextInt();
j= sc.nextInt();
weight= sc.nextInt();
arc[i][j] = weight;
}
return vnum; //返回顶点个数
}
int Backpath(int n) //求n个顶点的多段图的最短路径
{
int i, j, temp;
int [] cost=new int[N];
int [] path=new int[N]; //存储路径长度和路径
for(i = 0; i < n; i++) //初始化路径长度为∞
{
cost[i] = MAX;
path[i] = -1;
}
cost[0] = 0; //顶点0为源点
for(j = 1; j < n; j++) //执行填表工作
{
for(i = j - 1; i >= 0; i--) //考察所有入边
{
if (arc[i][j] + cost[i] < cost[j])
{
cost[j] = arc[i][j] + cost[i];
path[j] = i;
}
}
}
System.out.println(n-1); //输出终点
i = n-1;
while (path[i] >= 0) //依次输出path[i]
{
System.out.println("<-"+path[i]);
i = path[i]; //求得路径上顶点i的前一个顶点
}
System.out.println();
return cost[n-1]; //返回最短路径长度
}
}
from:算法设计与分析(第2版)——王红梅 胡明 编著——清华大学出版社