本章中有三个最短路径算法:
1、Bellman-Ford算法:解决的是一般情况下的单源最短路径问题,可适用于边的权重为负值,且有环路的情况,算法返回一个bool值,表明是否存在一个从源结点可以到达的权重为负值的环路。如果存在,则返回false,否则,可以求出最短路径和这条路径的权重。
2、Dag_Shortest_Paths: 解决有向无环图的单源最短路径问题,算法根据结点的拓扑排序对带权重的有向无环图G= (V, E)进行边的松弛操作,则可以在θ(V+E)的时间内计算出从单个源节点到所有结点之间的最短路径,允许有负权值的边存在。
3、Dijkstra算法:解决的是带权重的有向图,算法要去所有的权重大于0,运行时间小于Bellman-Ford算法,算法在运行过程中维持一组结点集合S,从源节点s到该集合中的每个结点之间的最短距离已经找到。算法重复从结点集V-S中选择最短路径估计中最小的结点u,将u加入集合S,然后对从u出发的边进松弛操作。
1、Bellman-Ford算法:(代码实现)
/**
Bellman-ford算法()
*/
#include <iostream>
using namespace std;
const int maxnum = 100;
const int maxint = 99999; //表示不可达
// 边,
typedef struct Edge{
int u, v; // 起点,终点
int weight; // 边的权值
}Edge;
Edge edge[maxnum]; // 保存边的值
int dist[maxnum]; // 结点到源点最小距离
int nodenum, edgenum, source; // 结点数,边数,源点
// 初始化图
void init()
{
cout << "请输入结点数,边数,源点:(三个正整数)" << endl;
// 输入结点数,边数,源点
cin >> nodenum >> edgenum >> source;
for (int i = 1; i <= nodenum; ++i)
dist[i] = maxint; //刚开始结点的距离都设置为不可达
dist[source] = 0;
cout << "请输入源结点(u),目标结点(V),边的权重(weight):(三个数(权值可以为负值))" << endl;
for (int i = 1; i <= edgenum; ++i)
{
cin >> edge[i].u >> edge[i].v >> edge[i].weight;
if (edge[i].u == source) //注意这里设置初始情况
dist[edge[i].v] = edge[i].weight;
}
}
// 松弛计算
void relax(int u, int v, int weight)
{
if (dist[v] > dist[u] + weight)
dist[v] = dist[u] + weight;
}
//BF算法主要的函数
bool Bellman_Ford()
{
for (int i = 1; i <= nodenum - 1; ++i) //遍历所有点
for (int j = 1; j <= edgenum; ++j) //遍历所有边(对每条边做松弛操作)
relax(edge[j].u, edge[j].v, edge[j].weight);
bool flag = 1;
// 判断是否有负环路
for (int i = 1; i <= edgenum; ++i)
{
if (dist[edge[i].v] > dist[edge[i].u] + edge[i].weight)
{
flag = 0;
break;
}
}
return flag;
}
int main()
{
init();
if (Bellman_Ford())
{
cout << "源结点是:" << source << endl;
cout << "源结点到各个结点(1,2,3,4,....)最短距离依次是:" << endl;
for (int i = 1; i <= nodenum; i++)
{
cout <<dist[i]<<endl;
}
}
return 0;
}
/**
例如算法导论:(图例子)
(5个点 10条边 1作为源结点)
5 10 1
1 2 6
1 5 7
2 5 8
2 3 5
2 4 -4
3 2 -2
4 3 7
4 1 2
5 4 9
5 3 -3
(5个点 10条边 2作为源结点)
5 10 2
1 2 6
1 5 7
2 5 8
2 3 5
2 4 -4
3 2 -2
4 3 7
4 1 2
5 4 9
5 3 -3
*/
运行结果如下:
2、Dag_Shortest_Paths+Floyd:(代码如下)
#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; //邻接矩阵的维度
};
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;
}
运行结果如下:
3、Dijkstra算法:(代码如下)
//Single_shortestPath.cpp : 定义控制台应用程序的入口点。
//Dijstra算法代码实现:
//Dijstra.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include<stack>
#define M 100
#define N 100
using namespace std;
//定义节点的数据类型
typedef struct node
{
int matrix[N][M]; //邻接矩阵
int n; //顶点数
int e; //边数
}MGraph;
//dijsktra主要的函数
void DijkstraPath(MGraph g, int *dist, int *path, int v0) //v0表示源顶点
{
int i, j, k;
bool *visited = (bool *)malloc(sizeof(bool)*g.n);
for (i = 0; i<g.n; i++) //初始化
{
if (g.matrix[v0][i]>0 && i != v0)
{
dist[i] = g.matrix[v0][i];
path[i] = v0; //path记录最短路径上从v0到i的前一个顶点
}
else
{
dist[i] = INT_MAX; //若i不与v0直接相邻,则权值置为无穷大
path[i] = -1;
}
visited[i] = false;
path[v0] = v0;
dist[v0] = 0;
}
visited[v0] = true;
for (i = 1; i < g.n; i++) //循环扩展n-1次
{
int min = INT_MAX;
int u;
for (j = 0; j < g.n; j++) //寻找未被扩展的权值最小的顶点
{
if (visited[j] == false && dist[j] < min)
{
min = dist[j];
u = j;
}
}
visited[u] = true;
for (k = 0; k<g.n; k++) //更新dist数组的值和路径的值 (松弛操作)
{
if (visited[k] == false && g.matrix[u][k]>0 && min + g.matrix[u][k] < dist[k])
{
dist[k] = min + g.matrix[u][k];
path[k] = u;
}
}
}
}
//打印最短路径上的各个顶点
void showPath(int *path, int v, int v0)
{
stack<int> s;
int u = v;
while (v != v0)
{
s.push(v);
v = path[v];
}
s.push(v);
while (!s.empty())
{
if (!s.empty())
{
cout << s.top() << "-> ";
s.pop();
}
}
}
//主函数
int _tmain(int argc, _TCHAR* argv[])
{
int n, e; //表示输入的顶点数和边数
while (cin >> n >> e&&e != 0)
{
int i, j;
int s, t, w; //表示存在一条边s->t,权值为w
MGraph g;
int v0;
int *dist = (int *)malloc(sizeof(int)*n);
int *path = (int *)malloc(sizeof(int)*n);
for (i = 0; i < N; i++)
for (j = 0; j < M; j++)
g.matrix[i][j] = 0;
g.n = n;
g.e = e;
for (i = 0; i < e; i++)
{
cin >> s >> t >> w;
g.matrix[s][t] = w;
}
cin >> v0; //输入源顶点
DijkstraPath(g, dist, path, v0);
for (i = 0; i < n; i++)
{
if (i != v0)
{
showPath(path, i, v0);
cout << "最短距离为:" << dist[i] << endl;
}
}
}
return 0;
}
//
//输入数据如下:构造5个节点,7条边的有向图
//5 7
//0 1 100
//0 2 30
//0 4 10
//2 1 60
//2 3 60
//3 1 10
//4 3 50
//0
//运行结果: