最短路径的结构:一条最短路径的所有子路径都是最短路径,
这篇中算法用到的都是邻接矩阵的存储方法,
0 if i == j
矩阵中w[i][j]: 有向边(i, j)的权重 if i != j 且(i, j) 属于E
INFINITE if i != j 且(i, j)不属于E
所有结点对最短路径问题的递归解:
设lij(m):是从结点i到结点j的至多包含m条变的任意路径中的最小权重,
当m = 0 时,结点i到结点j之间存在一条没有变的最短路径当且仅当i== j
if i== j, lij(m) = 0 else lij(m) = INFINITE
对于m >= 1, 需要计算的lij(m)是lij(m-1)的最小值,和从i到j最多有m条边组成的任意路径的最小权重,通过对j的所有前驱k检查来获得该值,
递归定义式是: lij(m) = min(lij(m-1), min(lik(m-1) + wkj) ) = min(lik(m-1) + wkj) ( 0 <= k <= n)
自底向上的计算最短路径的算法 :
核心算法是externShortestPaths():程序在给定W和L(m-1)的情况下计算出L(m), 代码将最近计算出的最短路径扩展了一条边。
算法用了三层循环,运行时间为Θ(n*n*n),该算法与矩阵相乘算法非常类似。
书中先介绍了一个Θ(n*n*n*n)时间内计算出最短路径的算法slowAllPathsShortestPaths,因为L(n-1)即为求出的最短路径矩阵,所以对externShortestPaths(),循环运行n-2次,一步一步扩展求出L(n-1),而L1 = W;
之后有介绍了一个改进算法运行时间的fastAllPathsShortestPaths算法,这个算法使用重复平方技术来计算矩阵序列,每次由两个L(m)得出L(2m),所以减小了需要求出的矩阵的个数,因为我们感兴趣的只是L(n-1),所以不用将每个矩阵都计算出来,而使用平方的方式,总共需要计算lg(n-1)个矩阵,而每个矩阵的计算时间是Θ(n*n*n),因此将算法运行时间减小到Θ(n*n*n*lgn)
之后就开始介绍到了Flod算法!!算法的运行时间是O(n*n*n), 考虑的是一条最短路径上的中间结点
使用的最短路径递归公式是与前面不同的,dij(k)为从结点i到结点j的所有中间结点全部取自集合{1, 2, ...., k}的一条最短路径的权重,
当k = 0 时,从结点i到结点j的一条不包括编号大于0的中间结点的路径将没有任何中间结点,dij(0) = wij
wij if k == 0
dij(k) =
min(dij(k-1), dik(k-1) + dkj(k-1)) if k >= 1
对于任何路径来说,所有的中间结点都属于集合{1, 2, ..., n},矩阵D(n)= (dij(n))给出的就是我们最后的答案,对于所有的i, j 属于V,dij(n)= δ(i,j)
Floyd算法又嵌套的三层for循环,所以运行时间为Θ(n*n*n)
关于最短路径的构建,可以在计算矩阵D(k)的同时计算前驱矩阵P,即也将计算一个矩阵序列P(0),P(1),...,P(n),P(n)定义pij(k)为从结点i到结点j的一条所有中间结点都取自集合{1,2,...,K}的最短路径上j的前驱结点。
pij(k)的递归公式是, 当k = 0时,从i到j没有中间结点,因此 NIL if i == j | wij = INFINITE
pij(0) = i if i != j && wij < INFINITE
如果k >= 1 pij(k-1) if dij(k-1) <= dik(k-1) + dkj(k-1) i到j的最短路径还是dij(k-1),所以j的前驱即为pij(k-1)
pij(k) =
pkj(k-1) if dij(k-1) > dik(k-1) + dkj(k-1) i到j的最短路径改为 dik(k-1) + dkj(k-1) ,有一个中间结点k,i到j的路径中的前驱即为k到j路径中j的前驱
代码中用到的图为:
以下为代码:
#ifndef MGRAPH_H #define MGRAPH_H #include <iostream> using namespace std; //枚举类型,图的种类 DG:有向图;WDG:带权值的有向图; //UDG: 无向图;WUDG: 带权值的无向图 enum GraphKind {DG, WDG, UDG, WUDG}; const int SIZE = 5; //定义二维数组的维度 typedef int (*pArray)[SIZE]; //定义二维数组返回指针 //vertexType顶点类型,VRType:顶点之间的关系类型,InfoType:弧的信息类型 template <typename VertexType> class MGraph { public: MGraph(int vexNum, GraphKind __kind) : vexnum(vexNum), arcnum(0), kind(__kind) { //分配顶点向量数组 vvec = new VertexType[vexnum]; //动态分配二维数组, 注意二维数组的动态分配 arcs = new int *[vexnum]; for (int i = 0; i < vexnum; i++) { //为每一行动态分配空间 arcs[i] = new int[vexnum]; } } //初始化邻接矩阵 void InitArcs() { for (int i = 0; i < vexnum; i++) { for (int j = 0; j < vexnum; j++) { if ((kind == WUDG || WDG) && i != j) arcs[i][j] = INFINITE; else arcs[i][j] = 0; } } } void CreateWDG1() { cout << "构造402页 带权有向图...." << endl; //构造顶点数组 for (int i = 0; i < vexnum; i++) { vvec[i] = 'a' + 0; } InitArcs(); //构造边 insertArc(0, 1, 3); insertArc(0, 2, 8); insertArc(0, 4, -4); insertArc(1, 3, 1); insertArc(1, 4, 7); insertArc(2, 1, 4); insertArc(3, 2, -5); insertArc(3, 0, 2); insertArc(4, 3, 6); cout << "带权有向图:" << endl; } //构造边 void insertArc(int vhead, int vtail, int weight) { arcs[vhead][vtail] = weight; arcnum++; } void displayGraph() { cout << "总共有" << vexnum << "个顶点," << arcnum << "条边" << endl; for (int i = 0; i < vexnum; i++) { cout << "第" << i+1 << "个顶点是:" << vvec[i] << "相邻的顶点有: "; for (int j = 0; j < vexnum; j++) { if (arcs[i][j] != INFINITE) cout << vvec[j] << "(" << arcs[i][j] << ") "; } cout << endl; } cout << "**********************************************" << endl; } /******************************************************************* 带返回值的结点对最短路径算法,二维数组全部采用动态分配new的方式申请,所以在 delete []之前,二维数组一直存在,所以可以当返回值传出去。 *******************************************************************/ int** externShortestPaths(int **L, int **W) { //分配Lnext数组, 根据L和arcs来计算出Lnext, 在L的基础上再多加一条边 int **Lnext = new int*[SIZE]; for (int i = 0; i < SIZE; i++) { Lnext[i] = new int[SIZE]; } for (int i = 0; i < vexnum; i++) { for (int j = 0; j < vexnum; j++) { Lnext[i][j] = INFINITE; for (int k = 0; k < vexnum; k++) { //Lnext[i][j]的值为L[i][k]每次加上一条边的权重的最小值 // 0 < k < vexnum,相当于将所有的边都加到原来的最小值上过一遍。 if (L[i][k] + W[k][j] < Lnext[i][j]) Lnext[i][j] = L[i][k] + W[k][j]; } } } return Lnext; } int** slowAllPairsShortestPaths() { cout << "slowAllPairsShortestPaths求出结点对之间的最短路径....." << endl; int **p; //指向前一个二维数组 p = arcs; displayTwoDimArray(p); //递归求出具有m条边的最小权值 for (int m = 2; m < vexnum; m++) { int **Lm; Lm = externShortestPaths(p, arcs); p = Lm; displayTwoDimArray(p); } return p; } //通过使用重复平方来计算矩阵 int** fastAllPairsShortestPaths() { cout << "fastAllPairsShortestPaths求出结点对之间的最短路径....." << endl; int **p; //指向前一个二维数组 p = arcs; displayTwoDimArray(p); //递归求出具有m条边的最小权值 for (int m = 2; m < vexnum; m++) { int **Lm; Lm = externShortestPaths(p, p); p = Lm; displayTwoDimArray(p); } return p; } /************************************************************************* Floyed算法: dk[i][j]:从结点i到结点j的所有中间结点全部取自于集合{1,2....k}的一条最短路径的权重 arcs[i][j] if k == 0 dk[i][j] = min(d(k-1)[i][j], d(k-1)[i][k] + d(k-1)[k][j]) if (k >= 1) 矩阵Dn = (d(n)[i][j])即为最后的答案 关于pi的求法: if k == 0 NULL if i = j | arcs[i][j] = INFINITE pi(0) = i if i != j && arcs[i][j] != INFINITE if k >= 1 pi(k-1)[i][j] if d(k-1)[i][j] <= d(k-1)[i][k] + d(k-1)[k][j] pi(k)[i][j] = pi(k-1)[k][j] if d(k-1)[i][j] > d(k-1)[i][k] + d(k-1)[k][j] **************************************************************************/ int** FloydWarshall() { int i, j, k; int **p = arcs; /*int **parr[SIZE+1];*/ /*parr[0] = p;*/ cout << "FloydWarshall初始的权重矩阵:" << endl; displayTwoDimArray(p); int **pi = new int *[SIZE]; for (i = 0; i < SIZE; i++) { pi[i] = new int[SIZE]; } //当k == 0时,初试化pi(0) for (i = 0; i < SIZE; i++) { for (j = 0; j < SIZE; j++) { if (i == j || arcs[i][j] == INFINITE) pi[i][j] = NULL; else pi[i][j] = i+1; } } cout << "d:" << endl; displayTwoDimArray(p); cout << "pi:" << endl; displayTwoDimArray(pi); for (k = 1; k <= SIZE; k++) { //构造D[k]和Pi[k] int **dk = new int *[SIZE]; for (i = 0; i < SIZE; i++) dk[i] = new int[SIZE]; int **pii = new int *[SIZE]; for (i = 0; i < SIZE; i++) pii[i] = new int[SIZE]; for (i = 0; i < SIZE; i++) { for (j = 0; j < SIZE; j++) { if (p[i][j] <= p[i][k-1] + p[k-1][j]) { dk[i][j] = p[i][j]; pii[i][j] = pi[i][j]; } else { dk[i][j] = p[i][k-1] + p[k-1][j]; pii[i][j] = pi[k-1][j]; } } } /*parr[k] = dk;*/ p = dk; pi = pii; cout << "d:" << endl; displayTwoDimArray(p); cout << "pi:" << endl; displayTwoDimArray(pi); } return p; } //输出一个二维数组 void displayTwoDimArray(int **p) { for (int i = 0; i < SIZE; i++) { for (int j = 0; j < SIZE; j++) cout << p[i][j] << " "; cout << endl; } cout << "~~~~~~~~~~~~~~~" << endl; } /******************************************************************* 不带返回值的结点对最短路径算法,二维数组是直接定义,属于局部定义,而要求 的矩阵也都是通过参数传递,而不是返回值,因为返回值不能返回一个局部的数组 *******************************************************************/ void externShortestPaths1(int (*L)[SIZE], int (*Lnext)[SIZE], int (*W)[SIZE]) { //Lnext数组, 根据L和arcs来计算出Lnext, 在L的基础上再多加一条边 for (int i = 0; i < vexnum; i++) { for (int j = 0; j < vexnum; j++) { if (i == j) Lnext[i][j] = 0; else Lnext[i][j] = INFINITE; for (int k = 0; k < vexnum; k++) { //Lnext[i][j]的值为L[i][k]每次加上一条边的权重的最小值 // 0 < k < vexnum,相当于将所有的边都加到原来的最小值上过一遍。 if (L[i][k] + W[k][j] < Lnext[i][j]) Lnext[i][j] = L[i][k] + W[k][j]; } } } } void slowAllPairsShortestPaths1() { cout << "slowAllPairsShortestPaths1求出结点对之间的最短路径....." << endl; int (*p)[SIZE]; int L1[SIZE][SIZE]; for (int i = 0; i < vexnum; i++) for (int j = 0; j < vexnum; j++) L1[i][j] = arcs[i][j]; p = L1; displayTwoDimArray1(p); //递归求出具有m条边的最小权值 for (int m = 2; m < vexnum; m++) { int Lm[SIZE][SIZE]; externShortestPaths1(p, Lm, L1); p = Lm; displayTwoDimArray1(p); } } void fastAllPairsShortestPaths1() { cout << "fastAllPairsShortestPaths1求出结点对之间的最短路径....." << endl; int (*p)[SIZE]; int L1[SIZE][SIZE]; for (int i = 0; i < vexnum; i++) for (int j = 0; j < vexnum; j++) L1[i][j] = arcs[i][j]; p = L1; displayTwoDimArray1(p); //递归求出具有m条边的最小权值 for (int m = 1; m < vexnum-1; m *= 2) { int Lm[SIZE][SIZE]; externShortestPaths1(p, Lm, p); p = Lm; displayTwoDimArray1(Lm); } } //输出一个二维数组,参数为指向二维数组的指针 void displayTwoDimArray1(int (*p)[SIZE]) { for (int i = 0; i < SIZE; i++) { for (int j = 0; j < SIZE; j++) cout << p[i][j] << " "; cout << endl; } cout << "~~~~~~~~~~~~~~~" << endl; } private: static const int INFINITE = 1000; //如果两个顶点之间可不达,则为该值 VertexType *vvec; //顶点向量 int **arcs; //邻接矩阵, 存放顶点关系,对带权图,为边权值 //对于无权图,用1或0表示,表示相邻与否; int vexnum; //图的当前顶点个数 int arcnum; //图的弧数 GraphKind kind; //图的种类标志 //const int SIZE; //邻接矩阵的维度 }; #endif
#include "MGraph.h" int main() { MGraph<char> wdgGraph(5, WDG); wdgGraph.CreateWDG1(); wdgGraph.displayGraph(); wdgGraph.slowAllPairsShortestPaths(); wdgGraph.slowAllPairsShortestPaths1(); wdgGraph.fastAllPairsShortestPaths(); wdgGraph.fastAllPairsShortestPaths1(); wdgGraph.FloydWarshall(); system("pause"); return 0; }
Floyd运行图: