一、迪杰斯特拉
求解图中由起点开始到其他节点的最短路径问题(权值不能为负)。
主要思想:在有权值的图中,直接和起点相连的节点他们的最短路径为和起点相连的边的权值,和起点没有直接相连的节点默认为该点和起点的距离为无穷大。分别保存在两个集合S,U中。S中保存的是已经计算出的距离起点最短路径的节点,U中保存还没有计算的节点。当集合U为空时,算法结束。

如上图,求解A点到其他节点的最短路径。
迪杰斯特拉算法求解过程:
1.将A节点放入到集合S中,其他节点放入集合U中,集合U中保存的节点的权值都是当前节点到起点的最短路径,例如上图中的节点E,当E放入集合U中时,E的权值应该为4,而不是3。因为节点A到E的最短路径为A-C-E。
S={A},U={B,C,D,E,F,G}; 集合U中各节点到起点的权值为UV={6,1,∞,∞,∞,∞},刚开始时,只有和起点直接相连的节点有权值,其他节点权值都默认为无穷大。
2.从集合U中找出权值最小的节点,加入到集合S中并且更新集合U,则S={A,C},U={B,D,E,F,G},此时也应该更新集合U中各节点到起点的权值,将与节点C相连的节点的权值由无穷大更新为经过C点到A点的路径上的权值的和,如果和值比之前的权值小,则更新,否则权值不变。因为和节点C相连的节点只有节点E(已经遍历过的节点不能重复计算),所以此时只需要更新集合U中节点E的权值即可,因为节点E当前的集合中权值为无穷大,所以更新节点E的权值为经过节点C到节点A的路径的权值的和,即为4,UV={6,∞,4,∞,∞}.
3.重复第2步操作,此时S={A,C,E},U={B,D,F,G},集合U中各节点的权值为UV={6,5,6,∞}.
4.重复第2步操作,此时S={A,C,E,D},U={B,F,G},因为节点D和节点B,E,F相连,所以需要更新节点B,E,F的权值,节点D到节点A的权值为5,节点D到节点B的权值为3,节点B经过节点D到节点A的权值为8,大于节点B已存在的到节点A的权值,所以节点B不更新,以此类推,集合U中各节点的权值为UV={6,6,∞}.
5.重复第2步操作,此时S={A,C,E,D,B},U={F,G},集合U中各节点的权值为UV={6,10}.
6.重复第2步操作,此时S={A,C,E,D,B,F},U={G},集合U中各节点的权值为UV={10}.
7.重复第2步操作,此时S={A,C,E,D,B,F,G},U={},集合U中各节点的权值为.
JAVA代码实现:
将上图保存在一个二维数组中(邻接矩阵):
// -1代表无穷大
static int[][] source;
public static void main(String[] args) throws Exception {
source = new int[][] {
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 6, 1, -1, -1, -1, -1 },
{ 0, 6, 0, -1, 3, -1, -1, 4 },
{ 0, 1, -1, 0, -1, 3, -1, -1 },
{ 0, -1, 3, -1, 0, 1, 4, -1 },
{ 0, -1, -1, 3, 1, 0, 2, -1 },
{ 0, -1, -1, -1, 4, 2, 0, 5 },
{ 0, -1, 4, -1, -1, -1, 5, 0 }
};
int l = dijktra(1, 6);
System.out.println(l);
}
public static int dijkstra(int start, int end) {
// 定义一个优先队列,其中数组的第一个元素为节点编号,第二个元素为当前节点距离起点的距离,所有节点到自身的距离都为0
PriorityQueue<int[]> que = new PriorityQueue<int[]>(new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
return o1[1] - o2[1];
}
});
que.add(new int[] { start, 0 });// 将起点加入队列中
while (!que.isEmpty()) {
int[] poll = que.poll();
if (poll[0] == end) {// 找到目标节点的最短路径poll[1],结束循环。
return poll[1];
}
for (int i = 1; i < source[poll[0]].length; i++) {
if (source[poll[0]][0] == 1) {// 用二维数组的第一列的值来记录当前节点是否被访问过,1:访问过,0:未访问过
break;
}
if (source[poll[0]][i] != -1 && poll[0] != i) {// 用-1代表无穷大
if (source[i][start] == -1) {// source[i][start]的值为-1,代表着当前节点到起点的距离为无穷大
que.add(new int[] { i, poll[1] + source[poll[0]][i] });
} else {
//将节点i原来距离起来start的距离和经过poll[0]节点距离start节点的距离相比取最小值。
que.add(new int[] { i, Math.min(poll[1] + source[poll[0]][i], source[i][start]) });
}
}
}
source[poll[0]][0] = 1;//表示该节点已被访问过
}
return -1;
}
二、地杰斯特拉(邻接链表)
/**
* 迪杰斯特拉 求解单元最短路径问题, 不能有负权边
*
*/
class dijkstra {
private int N, M, MAX = 100001;// N个节点 M条边 MAX:当前题目所能求得的最大值加1
/*
* 用邻接链表存储点与点之间的关系 eg: 1 2 5 节点1和节点2相连权值为5 1 3 1 source[1] = {[2,5],[3,1]}
*/
private ArrayList[] source = new ArrayList[N + 1];
/*
* visited[] 记录起点到其他节点的最短距离,eg: 起点为 1, visited[4]: 表示节点1到节点4的最短距离
* visited初始为当前题目中所能取到的最大值+1. 如果初始化为integer的最大值,可能会存在越界问题
*/
private int visited[] = new int[N + 1];
dijkstra(int start) {
PriorityQueue<int[]> pq = new PriorityQueue<int[]>(new Comparator<int[]>() {
// 排序 :权值越小,越靠前
@Override
public int compare(int[] o1, int[] o2) {
// TODO Auto-generated method stub
return o1[1] - o2[1];
}
});
Arrays.fill(visited, MAX);
visited[start] = 0;// 起点到起点的距离为0
pq.add(new int[] { start, 0 });// 起点到起点的距离为0
while (!pq.isEmpty()) {
int[] p = pq.poll();
ArrayList<int[]> list = source[p[0]];// 获取起点的所有子节点的集合
for (int i = 0; i < list.size(); i++) {
int[] next = list.get(i).clone();
next[1] += p[1];// next[1]: 起点到p[0]节点的距离和p[0] 到 next[0] 的距离之和 ==> 起点经过p[0] 点到达 next[0] 节点的距离
if (visited[next[0]] > next[1]) {// visited[next[0]] 起点到next[0] 的距离
visited[next[0]] = next[1];
pq.add(next);
}
}
}
}
}
三、Floyd(弗洛伊德)算法
弗洛伊德算法和迪杰斯特拉算法比较相似,他们都是求解最短路径的算法,不同点在于迪杰斯特拉是求解由指定起点到其他节点之间的最短距离,弗洛伊德求解的是图中所有节点之间的最短距离
弗洛伊德算法主要思想是:有权值的图中所有节点和节点之间直接相连的距离是已知(节点到自身的距离为0),没有直接相连的节点之间的距离是无穷大,在算法执行过程中引入中间节点K(节点K为图中的任意一个节点),经过节点K更新不直接相连的节点之间的距离,直到所有节点被更新,算法结束。

如上图,求解图中各节点之间的最短距离
步骤:
1.将上图转换为一个邻接矩阵

2.引入中间节点K,选取图中的节点A作为中间节点(当然也可以是图中的其他节点,都可以),经过节点A可以到达节点B和节点C,其他节点没有和节点A直接相连所以认为其他节点和节点A直接不可达,所以经过节点A,我们可以更新的节点间的距离只有节点B和节点C,用L表示节点之间的距离
L(A,A) = 0
L(A,B) = 6
L(A,C) = 1
L(A,D) = ∞
L(A,E) = ∞
L(A,F) = ∞
L(A,G) = ∞
L(B,C) = L(B,A) + L(A,C) = 7
L(B,D) = L(B,A) + L(A,D) = 6 + ∞ = ∞,节点B和节点D直接相连,那么L(B,D)的值为什么不是3,而是无穷大?因为必须经过中间节点A,所以L(B,D)的值在经过中间节点A后就变为无穷大,此时就不需要更新邻接矩阵,使得L(B,D)的值保持原来的值
以节点A为中间节点遍历后的邻接矩阵,其中只有L(B,C)的值发生了改变

3.以节点B作为中间节点,遍历后的邻接矩阵

以此类推,分别以图中的A,B,C,D,E,F,G为中间节点,求解图中各个节点之间的最短路径,算法结束。在求解过程中需要比较一下本次求解的距离和原来的距离,始终保留最小值。
4.最终结果,此时矩阵中保存的是各节点之间的最短距离

JAVA代码实现
在弗洛伊德算法中应该注意最大值的选取,如果选取的最大值为Integer.MAX_VALUE,应该注意越界的问题。
static int[][] source;
static int M = 99999;
public static void main(String[] args) {
source = new int[][] {
{ 0, 6, 1, M, M, M, M },
{ 6, 0, M, 3, M, M, 4 },
{ 1, M, 0, M, 3, M, M },
{ M, 3, M, 0, 1, 4, M },
{ M, M, 3, 1, 0, 2, M },
{ M, M, M, 4, 2, 0, 5 },
{ M, 4, M, M, M, 5, 0 }
};
floyd();
for (int[] a : source) {
System.out.println(Arrays.toString(a));
}
}
public static void floyd() {
for(int k = 0;k<source.length;k++) {//中间节点K的取值
for(int i = 0;i<source.length;i++) {//起始节点
for(int j = 0;j<source.length;j++) {//终点
source[i][j] = Math.min(source[i][j],source[i][k]+source[k][j]);
}
}
}
return;
}
运行结果:

本文介绍了迪杰斯特拉算法和弗洛伊德算法,用于求解图中的最短路径问题。迪杰斯特拉算法适用于求解起点到其他节点的最短路径,而弗洛伊德算法则可找到所有节点间的最短路径。通过邻接矩阵和邻接链表来表示图,并提供了JAVA代码实现。

6510

被折叠的 条评论
为什么被折叠?



