贪心算法求单源最短路径(迪杰斯特拉算法)

目录

1.单源最短路径问题描述

2.Dijkstra算法思想

3.具体案例分析

4.具体代码实现


1.单源最短路径问题描述

①给定带权有向图G =(V,E)。其中V是图中所有顶点的集合。E是图中所有边的集合,每条边的权是非负实数。

②给定V中的一个顶点,称为源。

③计算从源到所有其它各顶点的最短路长度。

而Dijkstra算法正是最具代表性的解单源最短路径问题的贪心算法。

2.Dijkstra算法思想

 ①设置顶点集合U,U表示已选择的节点,所以初始化的U中仅包含源节点。

②设置一张表,表中记录的是U外V内的节点到U中任意节点的距离的最小值。

③不断地作贪心选择,选择不在U集合中的节点来加入这个集合。

④每当一个节点加入这个集合后,更新其他未加入的节点到U中任意一点的距离的最小值。

⑤当所有节点都加入到U中后即算法完成。

疑问:

这样加入U之后跟路径有什么关系?就算跟路劲有关系能确保是最短路径吗?

我们从加入U的第一个节点剖析:

当要选择第一个加入U的节点时,我们只能选择跟源节点有路径关系的节点。

因为其他节点如果跟源节点没有路径关系的话,到源节点的距离是无穷大,所以只能等着后面以其他节点为媒介再跟源节点联系上。

而一开始就跟源节点有路径关系的节点中我们选择路劲最短的那一个节点加入到U中。

为什么呢?因为此时这个节点到源节点的距离肯定是最短的。

比如说节点a到源节点的距离是10,节点b到源节点的距离是20,其他节点到源节点的距离是无穷。如果此时不选a的话,后面想选a只有两种方法:要么直接通过b到源节点,要么先通过b的拓展节点再通过b到源节点,这两种的路径长度至少都是20+,比10大。所以此时将a加入U是最好的,a到源节点的路径也是最短的。

而对于不是第一次拓展的其他节点来说,过程也是一样的:

如果此时加入U的最短路径你不选,那后面要选他只能通过其他更长的路径来选。

这就证明了Dijkstra算法的正确性。

3.具体案例分析

4.具体代码实现

public class SingleSourceShortestPath {

	public static void main(String[] args) {

		//节点数量
		int num = 5;
		//源节点
		int sourcev = 1;
		
		/*
		 * 5个节点,但设邻接矩阵为c[6][6]
		 * 这样c[1][2]就表示第一个节点到第二个节点的距离,较好理解
		 * 故:
		 * 因没有第0个节点,邻接矩阵第一行、第一列作废,设为-1
		 * 因节点自身到自身无意义,邻接矩阵左至右对角线作废,设为-1
		 */
		int[][] c = {
   
   {-1,-1,-1,-1,-1,-1},
					 {-1,-1,10,-1,30,100},
					 {-1,10,-1,50,-1,-1},
					 {-1,-1,50,-1,20,10},
					 {-1,30,-1,20,-1,60},
					 {-1,100,-1,10,60,-1}};
		
		//结果
		int[][] result = dijkstra(num,c,sourcev);
		for (int i = 1; i <= num; i++) {
			System.out.print("节点" + i + ":");
			if(i == sourcev) {
				System.out.println("该节点为源节点!");
			}else {
				System.out.print("到源节点最短路径为" + i + "->");
				int index = result[0][i];
				while(index != sourcev) {
					System.out.print(index + "->");
					index = result[0][index];
				}
				System.out.println(sourcev + ",长度为" + result[1][i]);
			}
		}
	}

	/**
	 * 
	 * @param num 节点的数量
	 * @param c 邻接矩阵
	 * @param sourcev 源节点
	 * @return 一个数组,数组的第0行表示节点的前驱节点,数组的第1行表示源到节点的距离。
	 */
	private static int[][] dijkstra(int num, int[][] c, int sourcev) {
		
		int[][] prevANDdist = new int[2][num+1];
		boolean[] isChosen = new boolean[num+1];
		
		//初始化
		for (int i = 1; i < prevANDdist[0].length; i++) {
			if(i == sourcev) {
				prevANDdist[0][i] = -1;
				prevANDdist[1][i] = Integer.MAX_VALUE;
				isChosen[sourcev] = true;
			}else {
				if(c[i][sourcev] != -1) {
					prevANDdist[0][i] = sourcev;
					prevANDdist[1][i] = c[i][sourcev];
					isChosen[i] = false;
				}else {
					prevANDdist[0][i] = -1;
					prevANDdist[1][i] = Integer.MAX_VALUE;
					isChosen[i] = false;
				}
			}
		}
		
		//因为要依次加入除去源节点外的num-1个节点,所以要循环num-1次。
		for (int i = 1; i <= num-1; i++) {
			int tempv = 0;
			int tempdist = Integer.MAX_VALUE;
			//选取下一个节点
			for (int j = 1; j <= num; j++) {
				if(isChosen[j]==false && prevANDdist[1][j]<tempdist) {
					tempv = j;
					tempdist = prevANDdist[1][j];
				}
			}
			//确认下一个节点是谁后做出的对表的更新
			isChosen[tempv] = true;
			for (int j = 1; j <= num; j++) {
				if(isChosen[j]==false && c[tempv][j] != -1) {
					if(prevANDdist[1][tempv] + c[tempv][j] < prevANDdist[1][j]) {
						prevANDdist[0][j] = tempv;
						prevANDdist[1][j] = prevANDdist[1][tempv] + c[tempv][j];
					}
				}
			}
		}
		
		return prevANDdist;
	}
}

2021/11/27

觉得有帮助请点个赞或者收藏~

猜你喜欢

转载自blog.csdn.net/Cristiano2000/article/details/121575548