图——多源最短路径
1. 最短路径和矩阵乘法
1.1 最短路径的结构
首先,引入一个定理:对于图G=(V, E)的所有结点对最短路径问题,都满足:一条最短路径的所有子路径都是最短路径。
假定用一个
的邻接矩阵
来表示输入图,且
,该矩阵表示的是一个有n个结点的有向图
的边的权重。其中:
另外,用 来表示从结点 i 到结点 j 的最短路径权重。
考虑从结点 i 到结点 j 的一条最短路径 p ,假定 p 至多包含 m 条边,还假定没有权重为负值的环路,且 m 为有限值,那么有如下两种情况:
(1)如果 i = j ,则 p 的权重为0,且不包含任何边;
(2)如果 i j , 则可以将路径 p 分解为两部分:从结点 i 到结点 k 的一条最短路径 和结点 k 与结点 j 之间的边。其中,路径 至多包含 m 条边,且 。
1.2 多源最短路径的递归解
假设 为从结点 i 到结点 j 的至多包含 m 条边的任意路径中的最小权重。则可以分为以下两种情况:
(1)当 m = 0 时,有:
(2)当 m 1 ,则
因为对于所有的 j ,有 = 0 ,所以上式中后面的等式成立。
注意:如果图 G 中不包含权重为负值的环路,则对于每一对结点 i 和 j ,如果 < ,则从 i 到 j 之间存在一条最短路径。由于该最短路径是简单路径,其包含的边最多为 n-1 条。从结点 i 到结点 j 的由多于 n-1 条边构成的路径不可能有比从 i 到 j 的最短路径权重更小的权重。因此,真正的最短路径权重可由下面的公式给出:
= = = =
1.3 自底向上计算最短路径权重
根据输入矩阵 ,可以计算出矩阵序列 ,最后的矩阵 包含的是最短路径的实际权重。另外,对于所有的结点 i 和 j , ,即 。
该算法的核心伪代码如下,它可以在给定 W 和 即
的情况下计算出
,也就是说该伪代码将最近计算出的最短路径扩展了一条边:
上述过程类似于对两个矩阵求乘积。例如计算矩阵乘积 C = A * B ,其中 A 和 B 都为 n*n 的矩阵。那么对于 ,则是计算
其伪代码即为:
在计算所有结点对最短路径问题的时候,可以通过对最短路径一条边一条边地扩展来计算最短路径的权重。用
来表示由算法 EXTEND_SHORTEST_PATHS(A, B) 所返回的矩阵“乘积”,可以计算出下面由 n-1 个矩阵所构成的矩阵序列:
下面的伪代码程序在 O(
) 时间内算出最终的
:
1.4 改进算法的运行时间
由于对所有的 m
n-1 ,都有
。因此可以仅利用
个矩阵乘积来计算$ L^{(n-1)}$ 。计算方法如下,即利用重复平方法,其复杂度为 O(
):
其伪代码如下:
1.5 具体例子
1.6 代码
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <algorithm>
using namespace std;
#define MAXIMUM 10000
int W[25] = {0, 3, 8, MAXIMUM, -4,
MAXIMUM, 0, MAXIMUM, 1, 7,
MAXIMUM, 4, 0, MAXIMUM, MAXIMUM,
2, MAXIMUM, -5, 0, MAXIMUM,
MAXIMUM, MAXIMUM, MAXIMUM, 6, 0};
//结点从0开始
void EXTEND_SHORTEST_PATHS(int *L, int *W, int row) {
int i, j, k, a;
int *LL = new int(row*row);
for(i=0; i<row; i++) {
for(j=0; j<row; j++) {
LL[i*row+j] = MAXIMUM;
for(k=0; k<row; k++) {
a = L[i*row+k] + W[k*row+j];
LL[i*row+j] = min(LL[i*row+j], a);
}
}
}
for(i=0; i<row; i++) {
for(j=0; j<row; j++) {
L[i*row+j] = LL[i*row+j];
}
}
}
//时间复杂度为O(n^4)
void SLOW_ALL_PAIRS_SHORTEST_PATHS(int *L, int *W, int row) {
int i, j, k;
for(i=0; i<row; i++) {
for(j=0; j<row; j++) {
L[i*row+j] = W[i*row+j];
}
}
for(i=2; i<=row; i++) {
EXTEND_SHORTEST_PATHS(L, W, row);
}
}
//重复平方法
void FASTER_ALL_PAIRS_SHORTEST_PATHS(int *L, int *W, int row) {
int i, j, k;
for(i=0; i<row; i++) {
for(j=0; j<row; j++) {
L[i*row+j] = W[i*row+j];
}
}
j = 1;
while(j < row-1) {
EXTEND_SHORTEST_PATHS(L, L, row);
j = j * 2;
}
}
int main()
{
int* L = new int[25];
//SLOW_ALL_PAIRS_SHORTEST_PATHS(L, W, 5);
FASTER_ALL_PAIRS_SHORTEST_PATHS(L, W, 5);
int i, j, k;
for(i=0; i<5; i++) {
for(j=0; j<5; j++) {
cout<<L[i*5+j]<<" ";
}
cout<<endl;
}
return 0;
}