两种算法的动态演示:视频地址
另两种图的最短路径算法,贝尔曼-福特&SPFA:https://blog.csdn.net/namewdy/article/details/106330691
下面代码都以此图为例,求顶点A与顶点E的最短路径长度及最短路径。(最短路径:A B G F C I E,最短路径长度24)
迪杰斯特拉,也有人叫作狄克斯特拉,该算法是贪心思想的运用。具体表现在:如果源点A到终点B的距离最短,那么这条路径上的其它顶点到源点A的距离也是最短的。这点很容易理解,如果其它顶点到源点有更短的路径,那么A到B必然存在一条更小的最短路径。
迪杰斯特拉算法的求解步骤是:从源点出发,首先将离它距离最近的那个顶点加入到最短路径当中。然后以这个顶点为中间顶点对所有非最短路径上的顶点做一次松弛操作得到所有非最短路径上的顶点到源点的当前最短距离,把最小距离的那个顶点加入到最短路径当中。重复以刚加入的这个顶点作为中间顶点对剩下的所有非最短路径上的顶点做松弛操作,然后又找到一个最小距离的顶点作为最短路径上的顶点。这样每一轮松弛操作就能得到一个最短路径上的顶点,直到找全最短路径。
算法过程可以概括为深度搜索与贪心思想的结合。深度搜索体现在每确定一个最短路径上的顶点时,以该顶点为中间顶点对所有非最短路径上的顶点做一次松弛操作,确定所有非最短路径上的顶点到源点的当前最短距离。贪心体现在每次都是从这些当前最短距离中选距离最小的顶点加入到最短路径中。整个求解过程可以看作是逐步增加最短路径上的顶点的过程。
什么是松弛操作:对一条边(u, v)松弛操作,其实就是测试是否可以通过 u,对迄今找到的 v 的最短路径进行改进。例如用 dist[u], dist[v] 表示顶点 u, v 到源点的最短距离,且当前已求出 dist[u]=3, dist[v]=8,而顶点 u, v 之间有一条权值为2的边edge[u][v],由于 dist[u]+edge[u][v]
Dijkstra算法在具体实现时,需要设置3个辅助数组:
java代码:
public class Dijkstra {
char[] vertex = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I' };
int[][] matrix = new int[9][9];
int INF = 1 << 31 - 1; // INF表示正无穷
// 创建邻接矩阵
private void creatMartix() {
matrix[locate('A')][locate('B')] = matrix[locate('B')][locate('A')] = 4;
matrix[locate('A')][locate('G')] = matrix[locate('G')][locate('A')] = 8;
matrix[locate('B')][locate('G')] = matrix[locate('G')][locate('B')] = 3;
matrix[locate('B')][locate('C')] = matrix[locate('C')][locate('B')] = 8;
matrix[locate('G')][locate('F')] = matrix[locate('F')][locate('G')] = 1;
matrix[locate('G')][locate('H')] = matrix[locate('H')][locate('G')] = 6;
matrix[locate('C')][locate('F')] = matrix[locate('F')][locate('C')] = 2;
matrix[locate('F')][locate('H')] = matrix[locate('H')][locate('F')] = 6;
matrix[locate('C')][locate('D')] = matrix[locate('D')][locate('C')] = 7;
matrix[locate('C')][locate('I')] = matrix[locate('I')][locate('C')] = 4;
matrix[locate('H')][locate('I')] = matrix[locate('I')][locate('H')] = 2;
matrix[locate('D')][locate('I')] = matrix[locate('I')][locate('D')] = 14;
matrix[locate('D')][locate('E')] = matrix[locate('E')][locate('D')] = 9;
matrix[locate('E')][locate('I')] = matrix[locate('I')][locate('E')] = 10;
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix.length; j++) {
if (matrix[i][j] == 0) {
matrix[i][j] = INF;
}
}
}
}
private int locate(char v) {
int i = 0;
for (; i < vertex.length; i++) {
if (v == vertex[i])
break;
}
return i;
}
/*
* --------------------上面是创建邻接矩阵,以下是Dijkstra部分代码--------------------
*/
// dist[i]保存源点到顶点i的当前最短路径长度,初值为边上的权值
int[] dist = new int[vertex.length];
// path[j]保存源点与顶点j的当前最短路径中顶点j前驱顶点的编号,初值为源点start的编号或-1
int[] path = new int[vertex.length];
// 保存已确定的到源点距离最短的顶点集合
boolean[] set = new boolean[vertex.length];
private void dijkstra(char start, char end) {
// 初始化dist数组和set数组初值
for (int i = 0; i < vertex.length; i++) {
dist[i] = matrix[locate(start)][i];
set[i] = false;
// 到邻接点的最短路径前驱顶点设为源点,其余设为-1
if (matrix[locate(start)][i] < INF) {
path[i] = locate(start);
} else {
path[i] = -1;
}
}
// 源点最短距离设为0
dist[0] = 0;
// 源点加入set集合中
set[locate(start)] = true;
int u = 0;
int mindis;
// 遍历vertex.length-1次;每次找出最短路径中的一个顶点
for (int i = 1; i < vertex.length; i++) {
mindis = INF;
// 从所有未确定最短距离的顶点中找出距源点最短的那个顶点
for (int j = 0; j < vertex.length; j++) {
if (set[j] == false && dist[j] < mindis) {
u = j;
mindis = dist[j];
}
}
set[u] = true; // 将新确定最短路径的顶点加入到set集合中
// 以刚确定的最短距离顶点作为中间顶点,更新所有剩余的未确定最短距离的顶点到源点的最短距离。
// 具体是:如果源点经过该中间顶点到顶点j的距离小于原有到顶点j距离时,更新到j最短距离,
// 并将中间顶点作为到顶点j最短路径中顶点j的前驱顶点。否则不变。
for (int j = 0; j < vertex.length; j++) {
if (set[j] == false) {
if (matrix[u][j] < INF && dist[u] + matrix[u][j] < dist[j]) {
dist[j] = dist[u] + matrix[u][j];
path[j] = u;
}
}
}
}
System.out.println("最短路径长度:" + dist[locate(end)]);
getPath(end);
}
// 根据path数组打印路径
private void getPath(char end) {
System.out.print("最短路径:" + end);
int i = locate(end);
while (path[i] != -1) {
System.out.print("<--" + vertex[path[i]]);
path[i] = path[path[i]];
}
}
public static void main(String[] args) {
Dijkstra d = new Dijkstra();
d.creatMartix();
d.dijkstra('A', 'E');
}
}
算法分析
弗洛伊德算法采用动态规划思想,是由局部最优解推导出整体最优解的过程。用二维数组 dist 保存算法运行过程中任意两顶点间的最短路径权值,例如 dist[ i ][ j ] 的值表示编号为 i 的顶点到编号为 j 的顶点之间最短路径的权值。
dist 数组根据邻接矩阵初始化,如果顶点 i 到顶点 j 的存在边且权值为 w,则让 dist[ i ][ j ] = w,否则初始为无穷大。 然后对整个二维数组,每次通过以不同顶点作为中间顶点来更新整个 dist 数组。例如以编号为 k 的顶点作为中间顶点时,如果 dist[ i ][ j ] > dist[ i ][ k ] + dist[ k ][ j ],则说明顶点 k 更有可能是顶点 i 与顶点 j 的最短路径上的顶点。这里蕴含的思想是,如果顶点 k 是顶点 i 与顶点 j 最短路径上的顶点,那么顶点 i 到顶点 k 与顶点 k 到顶点 j 的路径同时都是最短的。
这样针对不同顶点对 dist 数组每进行一轮更新,就可以得到一个将该顶点考虑在内的最短路径权值的新 dist 数组,以此类推,直至将所有顶点考虑一遍,此时更新后得到的 dist 数组保存的就是将所有顶点考虑在内的最短路径权值。这里体现的就是上面所说的由局部最优解推导出整体最优解。
如果需要获取具体最短路径的话可以再增设一个二维数组 path[ ][ ],path[ i ][ j ] 表示顶点 i 到顶点 j 的最短路径中顶点 j 的前一个顶点的编号。初始化为如果顶点 i 到顶点 j 存在边的话用 path[ i ][ j ] 保存顶点 i 的编号,否则设为-1(起个标志作用,也可以是其它)。以后在对各个顶点考虑的时候,每次 dist[ i ][ j ] 的最短路径发生更新时都将 path[ i ][ j ] 修改为被考虑顶点的编号。最后依次往回推导得到最短路径上的所有顶点。
详见代码:
public class Floyd {
char[] vertex = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I' };
int[][] matrix = new int[9][9];
int INF = 1 << 31 - 1;
private void creatMartix() {
matrix[locate('A')][locate('B')] = matrix[locate('B')][locate('A')] = 4;
matrix[locate('A')][locate('G')] = matrix[locate('G')][locate('A')] = 8;
matrix[locate('B')][locate('G')] = matrix[locate('G')][locate('B')] = 3;
matrix[locate('B')][locate('C')] = matrix[locate('C')][locate('B')] = 8;
matrix[locate('G')][locate('F')] = matrix[locate('F')][locate('G')] = 1;
matrix[locate('G')][locate('H')] = matrix[locate('H')][locate('G')] = 6;
matrix[locate('C')][locate('F')] = matrix[locate('F')][locate('C')] = 2;
matrix[locate('F')][locate('H')] = matrix[locate('H')][locate('F')] = 6;
matrix[locate('C')][locate('D')] = matrix[locate('D')][locate('C')] = 7;
matrix[locate('C')][locate('I')] = matrix[locate('I')][locate('C')] = 4;
matrix[locate('H')][locate('I')] = matrix[locate('I')][locate('H')] = 2;
matrix[locate('D')][locate('I')] = matrix[locate('I')][locate('D')] = 14;
matrix[locate('D')][locate('E')] = matrix[locate('E')][locate('D')] = 9;
matrix[locate('E')][locate('I')] = matrix[locate('I')][locate('E')] = 10;
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix.length; j++) {
if (matrix[i][j] == 0) {
matrix[i][j] = INF;
}
}
}
}
private int locate(char v) {
int i = 0;
for (; i < vertex.length; i++) {
if (v == vertex[i])
break;
}
return i;
}
/*
* --------------------上面创建邻接矩阵,以下是Floyd部分代码--------------------
*/
int[][] dist = new int[vertex.length][vertex.length];
int[][] path = new int[vertex.length][vertex.length];
private void floyd(char start, char end) {
// 初始化
for (int i = 0; i < vertex.length; i++) {
for (int j = 0; j < vertex.length; j++) {
dist[i][j] = matrix[i][j];
if (i != j && matrix[i][j] < INF) {
path[i][j] = i;
} else {
path[i][j] = -1;
}
}
}
// 将所有顶点逐个作为中间顶点考虑
for (int k = 0; k < vertex.length; k++) {
for (int i = 0; i < vertex.length; i++) {
for (int j = 0; j < vertex.length; j++) {
// 如果两顶点i,j之间通过顶点k的最短路径小于原来的最短路径,更新dist数组的值,并设顶点k为顶点j的前驱顶点
if (i != j && dist[i][k] < INF && dist[k][j] < INF && dist[i][j] > dist[i][k] + dist[k][j]) {
dist[i][j] = dist[i][k] + dist[k][j];
path[i][j] = path[k][j];
}
}
}
}
System.out.println("最短路径长度:" + dist[locate(start)][locate(end)]);
getPath(start, end);
}
private void getPath(char start, char end) {
int i = locate(start);
int j = locate(end);
System.out.print("最短路径:" + end);
while (path[i][j] != -1) {
// 获取最短路径根据的是源点不变,每次变更新的终点,直至终点与源点重合。
System.out.print("<--" + vertex[path[i][j]]);
path[i][j] = path[i][path[i][j]];
}
// // 获取最短路径的方法2,根据源点在二维数组中下标一定相等
// while (i != j) {
// System.out.print("<--" + vertex[path[i][j]]);
// j = path[i][j];
// }
}
public static void main(String[] args) {
Floyd f = new Floyd();
f.creatMartix();
f.floyd('A', 'E');
}
}
算法分析