引入
狄杰斯特拉(Dijstra)算法解决的问题是:从图G中的某个指定顶点vk开始到其余各个顶点的最短路径,其中图G有n个顶点,k∈[0, n-1]。若还需要求某个顶点vx开始到其余各个顶点的最短路径(其中x≠k),则还需要再跑一次Dijstra算法。若用户需要图G中每个顶点到其余顶点最短路径呢?则需要跑n次Dijstra算法。由于Dijstra算法的时间复杂度为O(n2),所以跑n次,则时间复杂度为O(n3)。有没有一种算法能够只跑一次就获得所有顶点到其余顶点的最短路径呢?弗洛伊德(Floyd)算法!
弗洛伊德算法解决的问题是:具有n个顶点的图G中的所有顶点到其它所有顶点的最短路径。即一次性求得顶点vk到顶点集V-vk中各顶点的最短路径。例如:一次性求得,顶点v0到v0、v1、v2、v3、……、vn-1;v1到各顶点;v2到各顶点;………;vn-1到各顶点的最短路径。
弗洛伊德算法适用于需要求所有顶点至所有顶点的最短路径问题。并且既适用于无向图也适用于有向图。
核心思想
-
对于图G中相异的任意两个顶点vi和vj,其中i≠j,vi要么直接与vj相连,要么经过图G中的某个顶点vk与vj相连。若D(i, j) > D(i, k) + D(k, j),则vi经过vk到达vj比直接从vi到达vj的距离(或权值或随便你像赋予的任何意义)更短,那么记录下此时的D(i, j)。并用P(i, j)记录下此时的k,即vi到vk,再从vk到vj(vi和vk之间可能还有别的顶点,同理vk和vj之间也可能还有别的顶点。)(用D(i, j)表示顶点vi到顶点vj的距离。)
-
对于图G中相同的任意两个顶点vi和vj,其中i=j,自己到自己的距离一定是0,所以D(i, i)=D(j, j)=0,且P(i, i)=P(j, j)=i或j。举例:若v2到v2,则i=2,j=2;且不会经过任意别的顶点,只经过自己,即v2。
问题:下图中各顶点到其余各个顶点的最短路径是什么?
推演
用Path[i][j].Length表示vi->vj的路径长度;用Path[i][j].Via表示vi->vj经过顶点Via。(vi->Via之间可以有其它顶点;Via->vj
之间也可由其它顶点。)
用邻接矩阵graph表示图G。graph[i][j]表示vi->vj的权值(距离或任何你想赋予的含义。)
初始状态Path[i][j].Length==∞即vi->vj无路径(对于边来说∞表示无边)。
将顶点v0、v1、……、vn-1共n个顶点逐个加入到各条路径Path中,测试是否满足vi->vk->vj(即从顶点vi经过vk到达vj能否构成一条路径)且若Path[i][j].Length > Path[i][k].Length + Path[k][j].Length则Path[i][j].Length = Path[i][k].Length + Path[k][j].Length;Path[i][j].Via = k(输出结果时要用到递归。)或Path[i][j].Via=Path[i][k].Via(输出结果时代码简单。)
具体的动画演示步骤可参看大话数据结构里的PPT。链接:《大话数据结构 溢彩加强版》相关主题 - 伍迷 - 博客园 (cnblogs.com)
书中问题
若使用编程语言(计算机)能表示的最大整数表示无穷,则在比较路径的长短(权值的大小或你赋予的任何意义)时,可能会产生计算溢出。虽然书上(包括最新版的溢彩版(2020年12月第1印次))的代码使用65535来表示无穷(即216-1,16位无符号整型值的最大值)在32位整型值的系统上并无所谓,但是若权值大于65535呢?又或许原本作者就是想用最大整型值表示无穷?
我的疑问
这样写代码,最后输出的Paths数组确实更便于整理输出vi->vj的路径。可是P[v][w] = P[v][k]具体含义是什么?我不觉得是前驱。vi->vk之间可以有更多的顶点,vk->vj之间也可以有更多的顶点。
代码
用邻接矩阵来表示图G。如下:
C#代码
using System;
using System.Collections.Generic;
namespace Floyd
{
class Program
{
static void Main(string[] args)
{
const int inf = Constants.Infinity; // 另设常量表示无穷
const int numberOfVertexes = 9;
int[][] graph = new int[][] {
new int[]{0, 1, 5, inf, inf, inf, inf, inf, inf },
new int[]{ 1, 0, 3, 7, 5, inf, inf, inf, inf },
new int[]{ 5, 3, 0, inf, 1, 7, inf, inf, inf },
new int[]{ inf, 7, inf, 0, 2, inf, 3, inf, inf },
new int[]{ inf, 5, 1, 2, 0, 3, 6, 9, inf },
new int[]{ inf, inf, 7, inf, 3, 0, inf, 5, inf },
new int[]{ inf, inf, inf, 3, 6, inf, 0, 2, 7 },
new int[]{ inf, inf, inf, inf, 9, 5, 2, 0, 4 },
new int[]{ inf, inf, inf, inf, inf, inf, 7, 4, 0 },
};
Path[][] paths = Floyd(graph, numberOfVertexes);
Print(paths, numberOfVertexes);
}
/// <summary>
/// 使用Floyd算法计算含有numberOfVertexes个节点的图graph的各点到其余各点的最
/// 短路径。
/// </summary>
///
/// <param name="graph">以邻接矩阵表示的图graph。行下标为起点顶点的i,列下标
/// 为终点顶点。</param>
///
/// <param name="numberOfVertexes">图graph中的顶点个数。</param>
///
/// <returns>Path二维数组类。每个Path元素含有以行为下标作为起点顶点到以列为下
/// 标作为终点顶点的最短路径和途径顶点。</returns>
static Path[][] Floyd(int[][] graph, int numberOfVertexes)
{
Path[][] paths = new Path[numberOfVertexes][]; // 创建数组的第一维
for (int i = 0; i < numberOfVertexes; i++)
{
paths[i] = new Path[numberOfVertexes]; // 创建数组的第二维
for (int j = 0; j < numberOfVertexes; j++)
{
paths[i][j] = new Path
{
Length = Constants.Infinity, // 无穷表示无路径
Via = -1 // 不经过任何顶点
};
}
}
for (int i = 0; i < numberOfVertexes; i++) // 初始化
{
for (int j = 0; j < numberOfVertexes; j++)
{
if (graph[i][j] != Constants.Infinity)
{
paths[i][j].Length = graph[i][j]; // 图中现有各点的连
// 通情况
paths[i][j].Via = j; // 设终点即途经点
}
}
}
/**
* 测试Vi->Vk->Vj是否有连通的路径且路径更短。
*/
for (int k = 0; k < numberOfVertexes; k++) // 以Vk作为途经点。
{
for (int i = 0; i < numberOfVertexes; i++) // 以Vi作为起点。
{
for (int j = 0; j < numberOfVertexes; j++) // 以Vj作为终点。
{
if ((paths[i][k].Length != Constants.Infinity && // Vi->Vk有路径且
paths[k][j].Length != Constants.Infinity) && // Vk->Vj有路径
// 途经Vk的路径长度小于已知的Vi->Vj的路径。
(paths[i][k].Length + paths[k][j].Length < paths[i][j].Length))
{
paths[i][j].Length = paths[i][k].Length + paths[k][j].Length;
paths[i][j].Via = k;
/**
* 经过前面的这个路径到达现在的位置。书上用的这种方式。输
* 出路径的方式不同。
*/
//paths[i][j].Via = paths[i][k].Via;
}
//if ((paths[i][k].Length + paths[k][j].Length < paths[i][j].Length))
//{ // 重现加法溢出问题。
// paths[i][j].Length = paths[i][k].Length + paths[k][j].Length;
// paths[i][j].Via = k;
// /**
// * 经过前面的这个路径到达现在的位置。书上用的这种方式。输
// * 出路径的方式不同。
// */
// //paths[i][j].Via = paths[i][k].Via;
//}
}
}
}
/*
* 循环结束,找到图graph中各顶点到其余各顶点的最短距离(Length记录)及路径
* (Via记录)。
*/
return paths;
}
/// <summary>
/// 输出图中的各条路径。
/// </summary>
/// <param name="paths">已计算获得的路径。</param>
/// <param name="numberOfVertexes">图的顶点数目。</param>
static void Print(Path[][] paths, int numberOfVertexes)
{
for (int i = 0; i < numberOfVertexes; i++)
{
Console.WriteLine($"源点V{i}到其余各点的路径及距离:");
for (int j = 0; j < numberOfVertexes; j++)
{
//Console.WriteLine($"V{i}到V{j}:{paths[i][j].Length}");
//Console.WriteLine(PrintPath1Recursion(paths, i, j, numberOfVertexes));
Console.WriteLine(PrintPath1(paths, i, j, numberOfVertexes));
/**
* 《大话数据结构》的方式输出。
*/
//Console.WriteLine(PrintPath2(paths, i, j, numberOfVertexes));
}
Console.WriteLine();
}
}
/// <summary>
/// 输出Vi->Vj的路径及距离。(按自己思路的路径表示形式)
/// </summary>
/// <param name="paths">已计算获得的各路径的最短路径。</param>
/// <param name="start">起始顶点(下标)。</param>
/// <param name="end">终点顶点(下标)。</param>
/// <param name="numberOfVertexes">图G的顶点数目。</param>
/// <returns>描述路径及距离的字符串。如:0->1->2->4->3->6: 10</returns>
static string PrintPath1Recursion(Path[][] paths, int start, int end, int numberOfVertexes)
{
int i = start,
j = end;
int len = paths[i][j].Length;
string result = "";
if (paths[i][j].Via != j)
{
int k = paths[i][j].Via;
result = $"{PrintPath1Recursion(paths, i, k, numberOfVertexes)}";
result = $"{result}\n{PrintPath1Recursion(paths, k, j, numberOfVertexes)}";
}
else
{
result = $"{i}->{j}: {len}";
}
return result;
}
/// <summary>
/// 输出Vi->Vj的路径及距离。(按自己思路的路径表示形式)
/// </summary>
/// <param name="paths">已计算获得的各路径的最短路径。</param>
/// <param name="start">起始顶点(下标)。</param>
/// <param name="end">终点顶点(下标)。</param>
/// <param name="numberOfVertexes">图G的顶点数目。</param>
/// <returns>描述路径及距离的字符串。如:0->1->2->4->3->6: 10</returns>
static string PrintPath1(Path[][] paths, int start, int end, int numberOfVertexes)
{
int i = start,
j = end,
len = paths[start][end].Length,
k = paths[i][j].Via;
string result = "";
Stack<int> s = new Stack<int>();
s.Push(j);
while (s.Count != 0)
{
j = s.Pop();
if (paths[i][j].Via != j)
{
s.Push(j);
k = paths[i][j].Via;
s.Push(k);
}
else
{
result = string.IsNullOrEmpty(result) ? $"{i}" : $"{result}->{i}";
i = j;
}
}
result = $"{result}->{j}: {len}";
return result;
}
/// <summary>
/// 输出Vi->Vj的路径及距离。(按《大话数据结构》上的路径表示形式)
/// </summary>
/// <param name="paths">已计算获得的各路径的最短路径。</param>
/// <param name="start">起始顶点(下标)。</param>
/// <param name="end">终点顶点(下标)。</param>
/// <param name="numberOfVertexes">图G的顶点数目。</param>
/// <returns>描述路径及距离的字符串。如:0->1->2->4->3->6: 10</returns>
static string PrintPath2(Path[][] paths, int start, int end, int numberOfVertexes)
{
// 自己到自己和自己到别的顶点的情况。
int i = start, // Vi
j = end; // Vj
string result = i == j ? $"{i}->{j}" : $"{i}";
int len = paths[i][j].Length;
while (i != j)
{
i = paths[i][j].Via;
result = $"{result}->{i}";
}
result = $"{result}: {len}";
return result;
}
}
/// <summary>
/// 表示常量的类。
/// </summary>
public static class Constants
{
/// <summary>
/// 用整数的最大值表示无穷∞。
/// </summary>
public const int Infinity = int.MaxValue;
}
/// <summary>
/// 表示路径的类。
/// Length:点Vi->Vj的路径长度。
/// Via:顶点Vi->Via->Vj,即顶点Vi经过顶点Via到达顶点Vj。
/// Vi->Via之间可能还会有其它的顶点;Via->Vj之间可能还会有其它的顶点。
/// 输出路径时不能直接输出Vi->Via->Vj,要找到他们之间其它点。
/// </summary>
public class Path
{
/// <summary>
/// 顶点Vi->Vj的路径长度。至于i和j为多少?
/// 可以用Path类二维数组,行表示i,列表示j。
/// </summary>
public int Length { get; set; } = Constants.Infinity;
/// <summary>
/// 顶点Vi->Via->Vj,即顶点Vi经过顶点Via到达顶点Vj。
/// </summary>
public int Via { get; set; } = -1;
}
}
TypeScript代码
/**
* 用整数的最大值表示无穷∞。
*/
const infinity: number = Number.MAX_VALUE;
/**
* 表示路径的类。
* Length:点Vi->Vj的路径长度。
* Via:顶点Vi->Via->Vj,即顶点Vi经过顶点Via到达顶点Vj。Vi->Via之间可能还会有其它顶点;Via->Vj之间可能还会有其它的顶点。
* 输出路径时不能直接输出Vi->Via->Vj,要找到他们之间其它点。
*/
class Path {
/**
* 顶点Vi->Vj的路径长度。至于i和j为多少?可以用Path类二维数组,行表示i,列表示j。
*/
Length: number = infinity;
/**
* 顶点Vi->Via->Vj,即顶点Vi经过顶点Via到达顶点Vj。
*/
Via: number = -1;
}
/**
* 使用Floyd算法计算含有numberOfVertexes个节点的图graph的各点到其余各点的最短路径。
* @param graph 以邻接矩阵表示的图graph。行下标为起点顶点的i,列下标
* @param numberOfVertexes 图graph中的顶点个数。
* @returns Path二维数组类。每个Path元素含有以行为下标作为起点顶点到以列为下标作为终点顶点的最短路径和途径顶点。
* @author kokiafan
*/
function Floyd(graph: number[][], numberOfVertexes: number): Path[][] {
let paths: Path[][] = []; // 创建数组的第一维
for (let i = 0; i < numberOfVertexes; i++) {
paths[i] = []; // 创建数组的第二维
for (let j = 0; j < numberOfVertexes; j++) {
paths[i][j] = new Path();
paths[i][j].Length = infinity; // 无穷表示无路径
paths[i][j].Via = -1; // 不经过任何顶点
}
}
for (let i = 0; i < numberOfVertexes; i++) // 初始化
{
for (let j = 0; j < numberOfVertexes; j++) {
if (graph[i][j] != infinity) {
paths[i][j].Length = graph[i][j]; // 图中现有各点的连通情况
paths[i][j].Via = j; // 设终点即途经点
}
}
}
/**
* 测试Vi->Vk->Vj是否有连通的路径且路径更短。
*/
for (let k = 0; k < numberOfVertexes; k++) // 以Vk作为途经点。
{
for (let i = 0; i < numberOfVertexes; i++) // 以Vi作为起点。
{
for (let j = 0; j < numberOfVertexes; j++) // 以Vj作为终点。
{
if ((paths[i][k].Length != infinity && // Vi->Vk有路径且
paths[k][j].Length != infinity) && // Vk->Vj有路径
// 途经Vk的路径长度小于已知的Vi->Vj的路径。
(paths[i][k].Length + paths[k][j].Length < paths[i][j].Length)) {
paths[i][j].Length = paths[i][k].Length + paths[k][j].Length;
paths[i][j].Via = k;
/**
* 经过前面的这个路径到达现在的位置。书上用的这种方式。输
* 出路径的方式不同。
*/
//paths[i][j].Via = paths[i][k].Via;
}
// if ((paths[i][k].Length + paths[k][j].Length < paths[i][j].Length)) {
// // 重现加法溢出问题。
// paths[i][j].Length = paths[i][k].Length + paths[k][j].Length;
// paths[i][j].Via = k;
// /**
// * 经过前面的这个路径到达现在的位置。书上用的这种方式。输
// * 出路径的方式不同。
// */
// //paths[i][j].Via = paths[i][k].Via;
// }
}
}
}
/*
* 循环结束,找到图graph中各顶点到其余各顶点的最短距离(Length记录)及路径(Via记录)。
*/
return paths;
}
/**
* 输出Vi->Vj的路径及距离。(按自己思路的路径表示形式)
* @param paths 已计算获得的各路径的最短路径。
* @param start 起始顶点(下标)。
* @param end 终点顶点(下标)。
* @param numberOfVertexes 图G的顶点数目。
* @returns 描述路径及距离的字符串。如:0->1->2->4->3->6: 10
* @author kokiafan
*/
function PrintPath1Recursion(paths: Path[][], start: number, end: number, numberOfVertexes: number): string {
let i: number = start,
j: number = end,
len: number = paths[i][j].Length;
let result: string = "";
if (paths[i][j].Via != j) {
let k: number = paths[i][j].Via;
result = `${PrintPath1Recursion(paths, i, k, numberOfVertexes)}`;
result = `${result}\n${PrintPath1Recursion(paths, k, j, numberOfVertexes)}`;
}
else {
result = `${i}->${j}: ${len}`;
}
return result;
}
/**
* 输出Vi->Vj的路径及距离。(按自己思路的路径表示形式)
* @param paths 已计算获得的各路径的最短路径。
* @param start 起始顶点(下标)。
* @param end 终点顶点(下标)。
* @param numberOfVertexes 图G的顶点数目。
* @returns 描述路径及距离的字符串。如:0->1->2->4->3->6: 10
* @author kokiafan
*/
function PrintPath1(paths: Path[][], start: number, end: number, numberOfVertexes: number): string {
let i: number = start,
j = end,
len = paths[start][end].Length,
k = paths[i][j].Via;
let result: string = "";
let s: number[] = [];
s.push(j);
while (s.length != 0) {
j = s.pop();
if (paths[i][j].Via != j) {
s.push(j);
k = paths[i][j].Via;
s.push(k);
}
else {
if (!result) {
result = `${i}`;
}
else {
result = `${result}->${i}`;
}
i = j;
}
}
result = `${result}->${j}: ${len}`;
return result;
}
/**
* 输出Vi->Vj的路径及距离。(按《大话数据结构》上的路径表示形式)
* @param paths 已计算获得的各路径的最短路径。
* @param start 起始顶点(下标)。
* @param end 终点顶点(下标)。
* @param numberOfVertexes 图G的顶点数目。
* @returns 描述路径及距离的字符串。如:0->1->2->4->3->6: 10
* @author kokiafan
*/
function PrintPath2(paths: Path[][], start: number, end: number, numberOfVertexes: number): string {
// 自己到自己和自己到别的顶点的情况。
let i: number = start, // Vi
j = end; // Vj
let result: string = i == j ? `${i}->${j}` : `${i}`;
let len: number = paths[i][j].Length;
while (i != j) {
i = paths[i][j].Via;
result = `${result}->${i}`;
}
result = `${result}: ${len}`;
return result;
}
/**
* 输出图中的各条路径。
* @param paths 已计算获得的路径。
* @param numberOfVertexes 图的顶点数目。
* @author kokiafan
*/
function Print(paths: Path[][], numberOfVertexes: number): void {
for (let i = 0; i < numberOfVertexes; i++) {
console.log(`源点V${i}到其余各点的路径及距离:`);
for (let j = 0; j < numberOfVertexes; j++) {
//console.log(`V${i}到V${j}:${paths[i][j].Length}\n${PrintPath1Recursion(paths, i, j, numberOfVertexes)}`);
console.log(PrintPath1(paths, i, j, numberOfVertexes));
/**
* 《大话数据结构》的方式输出。
*/
//console.log(PrintPath2(paths, i, j, numberOfVertexes));
}
console.log();
}
}
function Main(): void {
const inf: number = infinity; // 另设常量表示无穷
const numberOfVertexes: number = 9;
let graph: number[][] = [
[0, 1, 5, inf, inf, inf, inf, inf, inf],
[1, 0, 3, 7, 5, inf, inf, inf, inf],
[5, 3, 0, inf, 1, 7, inf, inf, inf],
[inf, 7, inf, 0, 2, inf, 3, inf, inf],
[inf, 5, 1, 2, 0, 3, 6, 9, inf],
[inf, inf, 7, inf, 3, 0, inf, 5, inf],
[inf, inf, inf, 3, 6, inf, 0, 2, 7],
[inf, inf, inf, inf, 9, 5, 2, 0, 4],
[inf, inf, inf, inf, inf, inf, 7, 4, 0],
];
let paths: Path[][] = Floyd(graph, numberOfVertexes);
Print(paths, numberOfVertexes);
console.log(paths);
}
Main();
/**
PrintPath1方法的输出:
源点V0到其余各点的路径及距离:
0->0: 0
0->1: 1
0->1->2: 4
0->1->2->4->3: 7
0->1->2->4: 5
0->1->2->4->5: 8
0->1->2->4->3->6: 10
0->1->2->4->3->6->7: 12
0->1->2->4->3->6->7->8: 16
源点V1到其余各点的路径及距离:
1->0: 1
1->1: 0
1->2: 3
1->2->4->3: 6
1->2->4: 4
1->2->4->5: 7
1->2->4->3->6: 9
1->2->4->3->6->7: 11
1->2->4->3->6->7->8: 15
源点V2到其余各点的路径及距离:
2->1->0: 4
2->1: 3
2->2: 0
2->4->3: 3
2->4: 1
2->4->5: 4
2->4->3->6: 6
2->4->3->6->7: 8
2->4->3->6->7->8: 12
源点V3到其余各点的路径及距离:
3->4->2->1->0: 7
3->4->2->1: 6
3->4->2: 3
3->3: 0
3->4: 2
3->4->5: 5
3->6: 3
3->6->7: 5
3->6->7->8: 9
源点V4到其余各点的路径及距离:
4->2->1->0: 5
4->2->1: 4
4->2: 1
4->3: 2
4->4: 0
4->5: 3
4->3->6: 5
4->3->6->7: 7
4->3->6->7->8: 11
源点V5到其余各点的路径及距离:
5->4->2->1->0: 8
5->4->2->1: 7
5->4->2: 4
5->4->3: 5
5->4: 3
5->5: 0
5->7->6: 7
5->7: 5
5->7->8: 9
源点V6到其余各点的路径及距离:
6->3->4->2->1->0: 10
6->3->4->2->1: 9
6->3->4->2: 6
6->3: 3
6->3->4: 5
6->7->5: 7
6->6: 0
6->7: 2
6->7->8: 6
源点V7到其余各点的路径及距离:
7->6->3->4->2->1->0: 12
7->6->3->4->2->1: 11
7->6->3->4->2: 8
7->6->3: 5
7->6->3->4: 7
7->5: 5
7->6: 2
7->7: 0
7->8: 4
源点V8到其余各点的路径及距离:
8->7->6->3->4->2->1->0: 16
8->7->6->3->4->2->1: 15
8->7->6->3->4->2: 12
8->7->6->3: 9
8->7->6->3->4: 11
8->7->5: 9
8->7->6: 6
8->7: 4
8->8: 0
----------------------------------------
PrintPath2方法的输出:
源点V0到其余各点的路径及距离:
0->0: 0
0->1: 1
0->1->2: 4
0->1->2->4->3: 7
0->1->2->4: 5
0->1->2->4->5: 8
0->1->2->4->3->6: 10
0->1->2->4->3->6->7: 12
0->1->2->4->3->6->7->8: 16
源点V1到其余各点的路径及距离:
1->0: 1
1->1: 0
1->2: 3
1->2->4->3: 6
1->2->4: 4
1->2->4->5: 7
1->2->4->3->6: 9
1->2->4->3->6->7: 11
1->2->4->3->6->7->8: 15
源点V2到其余各点的路径及距离:
2->1->0: 4
2->1: 3
2->2: 0
2->4->3: 3
2->4: 1
2->4->5: 4
2->4->3->6: 6
2->4->3->6->7: 8
2->4->3->6->7->8: 12
源点V3到其余各点的路径及距离:
3->4->2->1->0: 7
3->4->2->1: 6
3->4->2: 3
3->3: 0
3->4: 2
3->4->5: 5
3->6: 3
3->6->7: 5
3->6->7->8: 9
源点V4到其余各点的路径及距离:
4->2->1->0: 5
4->2->1: 4
4->2: 1
4->3: 2
4->4: 0
4->5: 3
4->3->6: 5
4->3->6->7: 7
4->3->6->7->8: 11
源点V5到其余各点的路径及距离:
5->4->2->1->0: 8
5->4->2->1: 7
5->4->2: 4
5->4->3: 5
5->4: 3
5->5: 0
5->7->6: 7
5->7: 5
5->7->8: 9
源点V6到其余各点的路径及距离:
6->3->4->2->1->0: 10
6->3->4->2->1: 9
6->3->4->2: 6
6->3: 3
6->3->4: 5
6->7->5: 7
6->6: 0
6->7: 2
6->7->8: 6
源点V7到其余各点的路径及距离:
7->6->3->4->2->1->0: 12
7->6->3->4->2->1: 11
7->6->3->4->2: 8
7->6->3: 5
7->6->3->4: 7
7->5: 5
7->6: 2
7->7: 0
7->8: 4
源点V8到其余各点的路径及距离:
8->7->6->3->4->2->1->0: 16
8->7->6->3->4->2->1: 15
8->7->6->3->4->2: 12
8->7->6->3: 9
8->7->6->3->4: 11
8->7->5: 9
8->7->6: 6
8->7: 4
8->8: 0
*/
参考资料
《大话数据结构》 - 程杰 著 - 清华大学出版社 第268页