【数据结构】——图的最短路径算法(迪杰斯特拉+弗洛伊德)

两种算法的动态演示:视频地址

另两种图的最短路径算法,贝尔曼-福特&SPFA

下面代码都以此图为例,求顶点A与顶点E的最短路径长度及最短路径。(最短路径:A B G F C I E,最短路径长度24)
在这里插入图片描述

1.迪杰斯特拉(Dijkstra)

迪杰斯特拉,也有人叫作狄克斯特拉,该算法是贪心思想的运用。具体表现在:如果源点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]<dist[v],所以修改 dist[v]=dist[u]+edge[u][v]=5,即找到一条源点到顶点 v 的更短路径,路径经过边edge[u][v],这个过程就称为对边edge[u][v]的松弛操作。

Dijkstra算法在具体实现时,需要设置3个辅助数组:

  • set数组:用来标识某个顶点到源点的最短距离是否已确定,set[ i ]=true表示编号为 i 的顶点到源点的最短距离已确定。
  • dist数组:用来保存目前已求出的各个顶点到源点的最短距离,例如dist[ i ]存储编号为 i 的顶点到源点的目前最短距离。
  • path数组:用来保存最短路径。path[ i ]保存的是源点与编号为 i 的顶点的最短路径中顶点 i 的前一个顶点。这样便可以反推出任意一个顶点到源点最短路径上的所有顶点。

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的当前最短路径长度,初值为<start,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');
	}
}

算法分析

  • 算法中包含两重循环,所以时间复杂度 O ( n 2 ) Ο(n^2) O(n2)
  • 空间复杂度 O ( n ) Ο(n) O(n),为3个辅助数组所占的空间。
  • 适用条件:该算法不适用含有负边的图,因为Dijkstra算法中将所有顶点分为已求得最短路径的顶点集合(set)和未确定最短路径的顶点集合。归入set集合顶点的最短路径及其长度不再改变,如果边上的权值允许为负值,那么有可能出现set内某顶点(记为a)的最短路径长度加上这条负边的权值结果小于a原先确定的最短路径长度,而此时a在Dijkstra算法下是无法更新的,由此便可能得不到正确的结果。
  • Dijkstra算法属于图的单源最短路径算法,得到的是一个数组,表示源点到其它各点的最短距离。 如果只是想得到某两个顶点间的最短路径,只需要在每次新确定一个最短路径的顶点时判断这个顶点是不是终点,是的话直接break退出循环。

2.弗洛伊德(Floyd)

弗洛伊德算法采用动态规划思想,是由局部最优解推导出整体最优解的过程。用二维数组 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');
	}
}

算法分析

  • 代码中包含三层for循环,所以时间复杂度 O ( n 3 ) Ο(n^3) O(n3)
  • 空间复杂度 O ( n 2 ) Ο(n^2) O(n2),为两个二维数组的空间。
  • 适用条件:该算法允许图含有负边,但不允许含有负回路。这是因为如果回路权值为负,那么走一次回路,路径长度一定比上一次小,而算法本身并不会为此多计算一次这个回路。所以这里的不适用不是指程序不能运行,而是指可能得不到正确结果。
  • Floyd算法属于图的多源最短路径算法,得到的是一个矩阵,表示任意两点间的最短距离。 对比Floyd和Dijkstra两种算法,如果需要求的是图中所有两顶点间的最短距离,Dijkstra算法需要调用 n 次(n是顶点个数),时间复杂度同样也是 O ( n 3 ) Ο(n^3) O(n3)。但因为Floyd算法使用动态规划,求整体最优解利用了局部最优解的结果,而Dijkstra算法每次都是从头开始求,所以如果是求所有两顶点之间的最短路径,弗洛伊德算法的实际运行时间比调用 n 次迪杰斯特拉算法的时间快很多。
  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 迪杰斯特拉算法是一种用于寻找最短路径算法。它的工作原理是每次找出距离起点最近的未访问的顶点,并标记它已经被访问。然后更新其他顶点的距离,即如果从起点经过这个被访问的顶点可以更新它们的距离,则更新它们的距离。这个过程会一直进行直到所有的顶点都被访问过。 下面是一个 Java 的实现例子: ``` public class Dijkstra { public static void main(String[] args) { // 邻接矩阵表示 int[][] graph = { {0, 4, 0, 0, 0, 0, 0, 8, 0}, {4, 0, 8, 0, 0, 0, 0, 11, 0}, {0, 8, 0, 7, 0, 4, 0, 0, 2}, {0, 0, 7, 0, 9, 14, 0, 0, 0}, {0, 0, 0, 9, 0, 10, 0, 0, 0}, {0, 0, 4, 14, 10, 0, 2, 0, 0}, {0, 0, 0, 0, 0, 2, 0, 1, 6}, {8, 11, 0, 0, 0, 0, 1, 0, 7}, {0, 0, 2, 0, 0, 0, 6, 7, 0} }; int[] dist = dijkstra(graph, 0); for (int i = 0; i < dist.length; i++) { System.out.println("0 到 " + i + " 的最短距离为:" + dist[i]); } } public static int[] dijkstra(int[][] graph, int src) { int n = graph.length; int[] dist = new int[n]; // 标记是否已访问 boolean[] visited = new boolean[n]; // 初始化距离 for (int i = 0; i < n; i++) { dist[i] = Integer.MAX_VALUE; } dist[src] = 0; ### 回答2: 迪杰斯特拉算法是一种用于解决最短路径问题的算法。它的核心思想是从起始点开始,逐步寻找到其他节点的最短路径,并将找到的路径上的节点标记为已访问。该算法需要一个数据结构来表示节点和边的关系,并使用一个数组来记录每个节点的最短距离。在Java实现迪杰斯特拉算法可以采用以下步骤。 1. 首先,创建一个方法来实现迪杰斯特拉算法。该方法接受一个数据结构、起始点和终点作为参数。 2. 初始化一个距离数组,用于记录起始点到每个节点的最短距离,默认值为无穷大。 3. 将起始点的最短距离设为0,并将其标记为已访问。 4. 创建一个优先队列(PriorityQueue)用于存储待访问的节点,按照最短距离从小到大排序。 5. 将起始点加入优先队列。 6. 循环执行以下步骤,直到优先队列为空: - 通过优先队列的头部节点,获取当前最短距离的节点。 - 遍历该节点的邻居节点,计算从起始点经过当前节点到邻居节点的距离。 - 如果通过当前节点到邻居节点的距离小于邻居节点的最短距离,则更新邻居节点的最短距离。 - 将邻居节点加入优先队列。 7. 返回终点的最短距离。 以上是实现迪杰斯特拉算法的大致思路,具体的实现需要根据具体情况进行调整和细化。通过迪杰斯特拉算法,我们可以在一个加权寻找到起始点到终点的最短路径。这个算法在路径规划等领域有着广泛的应用。 ### 回答3: 迪杰斯特拉算法是一种常用的算法,用于求解单源最短路径问题。在Java,可以通过以下步骤实现迪杰斯特拉算法: 1. 首先,创建一个的类,用于表示的结构和边的权重。可以使用邻接矩阵或邻接表等数据结构存储的信息。 2. 创建一个长度为顶点数量的数组,用于存储顶点到源顶点的最短距离。初始化数组,将源顶点的距离设置为0,其他顶点的距离设置为无穷大。 3. 创建一个优先队列或最小堆,用于按照顶点到源顶点的距离进行排序。 4. 将源顶点加入优先队列或最小堆。 5. 当优先队列或最小堆不为空时,循环执行以下步骤: - 从优先队列或最小堆取出距离源顶点最近的顶点。 - 遍历该顶点的所有邻接顶点,计算从源顶点到这些邻接顶点的距离。 - 如果计算得到的距离小于当前保存的距离,则更新距离数组。 - 将邻接顶点加入优先队列或最小堆。 6. 循环结束后,距离数组保存的就是源顶点到各个顶点的最短距离。 以上就是利用迪杰斯特拉算法求解最短路径的Java实现方法。通过不断更新最短路径信息,迪杰斯特拉算法可以找到源顶点到任意顶点的最短路径。在实际应用迪杰斯特拉算法可以用于路由选择、网络拓扑分析等领域。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值