最短路径问题
用于计算一个节点到其他所有节点的最短路径。主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。Dijkstra算法能得出最短路径的最优解,但由于它遍历计算的节点很多,所以效率低。
迪杰斯特拉算法
*迪杰斯特拉算法(Dijkstra) 是典型的最短路径路由算法,用于计算一个节点到其他所有节点的最短路径。主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。Dijkstra算法能得出最短路径的最优解.
Dijkstra算法是很有代表性的最短路算法,在很多专业课程中都作为基本内容有详细的介绍,如数据结构,图论,运筹学等等。
算法思路
这里借鉴Ouyang_Lianjun的博客进行描述:
Dijkstra算法采用的是一种贪心的策略,声明一个数组dis来保存源点到各个顶点的最短距离和一个保存已经找到了最短路径的顶点的集合:T
初始时,原点 s 的路径权重被赋为 0 (dis[s] = 0)。
若对于顶点 s 存在能直接到达的边(s,m),则把dis[m]设为w(s, m),同时把所有其他(s不能直接到达的)顶点的路径长度设为无穷大。
初始时,集合T只有顶点s。
然后,从dis数组选择最小值,则该值就是源点s到该值对应的顶点的最短路径,并且把该点加入到T中,OK,此时完成一个顶点,然后,我们需要看看新加入的顶点是否可以到达其他顶点并且看看通过该顶点到达其他点的路径长度是否比源点直接到达短,如果是,那么就替换这些顶点在dis中的值。
然后,又从dis中找出最小值,重复上述动作,直到T中包含了图的所有顶点。
如果觉得这个听的云里雾里,那么我们再看一下接下来的解释:
首先有一个带权无向图(这里不考虑负权的情况)
假设我们求0到各个点的最短距离,其步骤应该如下:
-
准备三个一维数组,分别是isGetShortTest,用来判断该节点是否已经计算出最短路径了;shortTest当前所求到的最短路径,注意是当期,意味着这个数组的内容是会动态变化的;pre 当前情况下所有节点在最短路径上的直接前驱节点
-
上面三个数组的初值分别为:
isGetShortTest全为未找到,具体怎么定义自行决定。
shortTest初始化为邻接矩阵中初始节点对应的那一行。
pre初始化全为起始节点。 -
从指定点开始(第一次为起始点)开始,找到与他权值最小的那条边
-
标记这条边的另一个端点为已求得最短路径
-
动态更新shortTest,如果从新的这个路径得到的权值和小于上一次的权值和,则覆盖掉上次的值
-
将指定点变为最小权值边的另一个端点
-
重复3-6,知道所有路径都算出来了
我们用上图的例子进行一次推导:
三个一维数组初始化:
首先邻接矩阵是:
初始化后的三个数组:
shortTest:[0, 1, 5, ∞, ∞, ∞, ∞, ∞, ∞]
isGetShortTest:
[true, false, false, false, false, false, false, false, false]
这里使用的Java语言进行描述,使用boolean数组进行存储
pre:[0, 0, 0, 0, 0, 0, 0, 0, 0]
找到权值最小的边
由图易知,最小的权值为1
标记并更新
shortTest:[0, 1, 4, 8, 6, ∞, ∞, ∞, ∞]
isGetShortTest:
[true, true, false, false, false, false, false, false, false]
pre:
[0, 0, 1, 1, 1, 0, 0, 0, 0]
这里需要说明一下,因为默认初始化的值是邻接矩阵中起始点开始的那一列,就已经相当于进行了一次的更新,所以这里的更新相当于是整个算法的第二次
分析数据:
shortTest:
上一次:[0, 1, 5, ∞, ∞, ∞, ∞, ∞, ∞]
这一次:[0, 1, 4, 8, 6, ∞, ∞, ∞, ∞]
由图可知:
所有与1有连接关系的点进行这样的判断:0到1的距离加上1到它们自身的距离之和如果小于上一次存储的最短路径的值,则覆盖掉。上一次存储的值在shortTest里面,小于则修改这个数组。同时修改前驱节点。
重复上述步骤
得到:
shortTest:[0, 1, 4, 7, 5, 8, 10, 12, 16]
isGetShortTest:
[true, true, true, true, true, true, true, true, true]
pre:
[0, 0, 1, 4, 2, 4, 3, 6, 7]
代码描述
算法执行方法
public static void djs(int begin){
//初始化
init(map.length,begin);
//往后查找的依据就是shortTest,不断更新
for(int i =0;i<map.length;i++){
//代表无限大
int min_val = 55555;
//
int min_key = begin;
//找到最小权值的那条边
for(int j=0;j<map.length;j++){
if(!isGetShirtTest[j]&&shortTest[j]<min_val){
min_val = shortTest[j];
min_key = j;
}
}
//将这个边设置为已走
isGetShirtTest[min_key] = true;
//更新最短路径
for(int j=0;j<map.length;j++){
if(!isGetShirtTest[j]&&min_val+map[min_key][j]<shortTest[j]){
shortTest[j] = min_val+map[min_key][j];
pre[j] = min_key;
}
}
}
}
初始化方法:
public static void init(int n,int from){
isGetShirtTest = new boolean[n];
pre = new int[n];
//直接将开始的那一行进行赋值
shortTest = map[from].clone();
}
整体代码:
package algorithm;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Arrays;
import java.util.Scanner;
public class Dijkstra {
/**
* 是否已经找到最短路径
*/
static boolean[] isGetShirtTest;
/**
* 该点最短路径对应的前驱结点
*/
static int[] pre;
/**
* 相对最短路径
*/
static int[] shortTest;
static int[][] map;
public static void main(String[] args) throws FileNotFoundException {
//文末附数据
Scanner in = new Scanner(new File("src/algorithm/input3.txt"));
int n = in.nextInt();
Graph graph = new Graph(n);
map = graph.edges;
load(in,graph);
djs(0);
System.out.println(Arrays.toString(shortTest));
System.out.println(Arrays.toString(pre));
System.out.println(Arrays.toString(isGetShirtTest));
}
public static void djs(int begin){
init(map.length,begin);
for(int i =0;i<map.length;i++){
int min_val = 55555;
int min_key = begin;
//找到最小权值的那条边
for(int j=0;j<map.length;j++){
if(!isGetShirtTest[j]&&shortTest[j]<min_val){
min_val = shortTest[j];
min_key = j;
}
}
//将这个边设置为已走
isGetShirtTest[min_key] = true;
//更新最短路径
for(int j=0;j<map.length;j++){
if(!isGetShirtTest[j]&&min_val+map[min_key][j]<shortTest[j]){
shortTest[j] = min_val+map[min_key][j];
pre[j] = min_key;
}
}
}
}
public static void init(int n,int from){
isGetShirtTest = new boolean[n];
pre = new int[n];
//直接将开始的那一行进行赋值
shortTest = map[from].clone();
}
private static void load(Scanner in,Graph graph){
int m = in.nextInt();
for(int i=0;i<m;i++){
int x = in.nextInt();
int y = in.nextInt();
int weight = in.nextInt();
graph.addEdge(x,y,weight);
}
}
private static class Graph{
//邻接矩阵
int[][] edges;
//存放节点数据
int[] vertexes;
public Graph(int vertex){
edges = new int[vertex][vertex];
vertexes = new int[vertex];
//初始化矩阵
for (int i=0;i<vertex;i++){
for (int j=0;j<vertex;j++){
edges[i][j] = 55555;
if(i==j){
edges[i][j] = 0;
}
}
}
}
public void addEdge(int x,int y,int weight){
edges[x][y] = weight;
edges[y][x] = weight;
}
void print(){
for(int[] e:edges){
System.out.println(Arrays.toString(e));
}
}
}
}
其后就可以利用前驱节点来回溯找到具体某个点到某个点的具体路径
题中的输入数据:
9 16
0 1 1
0 2 5
1 2 3
1 3 7
1 4 5
2 4 1
2 5 7
3 6 3
3 4 2
4 6 6
4 7 9
4 5 3
5 7 5
6 7 2
6 8 7
7 8 4