Dijkstra(迪杰斯特拉)算法
迪杰斯特拉算法是经典的最短路径算法,用于计算一个节点到其他节点的最短路径。它主要的特点是以起始点为中心向外层层扩展(广度优先搜索算法),直到扩展到终点为止。
案例:最短路径问题
这里有6个村庄,分别是A、B、C、D、E、F,从G点出发需要将邮件分别送到这6个村庄,各个村庄有不同的路线,路线上都标有公里数,计算G村庄到各个村庄的最短距离?如果从其他点出发,各个点的最短距离又是多少?
. 思路分析
整体思路:
- 选择G为起始点,那么G就设为已访问,用一个记录路径的数组存储每个节点到达初始节点G的路径,首先以G为起始点,与G有连通的节点,就将对应的节点的路径填入数组,并将这些可连通的节点的前驱节点设置为G。然后再从其中选择一个没有访问的路径最短的节点作为下一个起点,即A点。现在就是走了G-A这条路,A设置为已访问,然后获取没有访问过的且与A有连通的节点的路径,然后这路径也会与该连通的节点的原始路径比较,小的就赋值给这个连通的节点,并将这些可连通的节点的前驱节点设置为A。依次类推…
案例具体思路:
有数组 :
int[] isVisitedVertex // 记录访问的节点,1为已访问,0为没访问
int[] vertexDis // 记录从G点到每个点的最短路径距离,默认值存入一个比较大的数65535表示无穷
int[] preVertex // 记录每个节点的前驱节点
A、B、C、D、E、F、G这几个点对应的索引下标就是0、1、2、3、4、5、6
- 首先看图1,从G点出发,将G点设置为已访问,由于G点没有前驱节点,所以它的前驱节点可以设置为它自己,即preVertex[6]=6,以G点进行广度优先遍历,满足可连接且没有被访问过的点,得到了A、B、E、F这四个点可以与G连通,并且路径分别是2、3、4、6,存入到对应索引下的vertexDis数组,即
vertexDis[0]=2; vertexDis[1]=3; vertexDis[4]=4; vertexDis[5]=6
,这四个节点的前驱节点更新为G的索引。图2,蓝色表示的已存入到vertexDis数组到达这四个顶点的距离。在没有被访问的路径下,选择一个已有路径最小的节点,所以会选择到A。
- 那么A就成为新的起点,将A标记已访问,以A点进行广度优先遍历,满足可连接且没有被访问过的点,得到了C、B两个点,路径分别是7、5。比较B已存在的距离是3,那么从A点到达B点,长度为A已走的距离加上A到B的距离即7,大于B已存在的距离3,所以B的距离不会发生变化;C已存在的距离是默认值65535,此时从G到C的距离是A已走的距离加上A到C的距离即9,小于C已存在的距离,所以C的最新距离就是9,因此C的前驱节点就变成了A,即preVertex[2]=0,并将距离更新到vertexDis数组中。因此得到了图4的结果。在没有被访问的路径下,选择一个已有路径最小的节点,所以会选择到B
- 重复第二步操作。得到图6。在没有被访问的路径下,选择一个已有路径最小的节点,所以会选择到E
- 重复第二步操作。得到图6。在没有被访问的路径下,选择一个已有路径最小的节点,所以会选择到F
- 重复第二步操作。得到图6。在没有被访问的路径下,选择一个已有路径最小的节点,所以会选择到C
- 重复第二步操作。得到图6。在没有被访问的路径下,选择一个已有路径最小的节点,所以会选择到D,最终D会被标记已访问。最终从G到每个点的最短路径都出来了。
. 代码实现
/**
* Created with IntelliJ IDEA
* User: heroC
* Date: 2020/6/13
* Time: 12:44
* Description:迪杰斯特拉算法
* 求某个顶点到达各个顶点的最短路径?
* 1,创建一个图,构建顶点数据和邻接矩阵
* 2,构建一个访问类,有3个主要的数组,分别是记录已被访问的数组、记录前驱节点的数组、已走的记录路径值
* 3,创建一个更新路径的方法,即选取一个最小的当前点到下一个没有访问的点的距离
* 4,创建一个获取没有被访问过的最小路径的新的节点为起点的方法
* Version: V1.0
*/
public class Dijkstra {
// NIF常量表示没有该条路径
private static final int NIF = 65535;
public static void main(String[] args) {
char vertexData[] = {'A','B','C','D','E','F','G'};
int weight[][] = {
{NIF, 5, 7, NIF, NIF, NIF, 2},
{5, NIF, NIF, 9, NIF, NIF, 3},
{7, NIF, NIF, NIF, 8, NIF, NIF},
{NIF, NIF, NIF, 4, 5, NIF, 6},
{NIF, NIF, 8, NIF, NIF, 5, 4},
{NIF, NIF, NIF, 4, 5, NIF, 6},
{2, 3, NIF, NIF, 4, 6, NIF}
};
// 迪杰斯特拉算法
dijkstraAlgorithm(vertexData,weight,6);
}
/**
* 迪杰斯特拉算法
* @param vertexData 顶点数据
* @param weight 邻接矩阵,权的关系
* @param start 起始顶点的索引
*/
public static void dijkstraAlgorithm(char[] vertexData, int[][] weight, int start){
// 构建一个图
DijGraph dijGraph = new DijGraph(vertexData, weight);
// 将图传递给Visited类,做处理
Visited visited = new Visited(start, dijGraph);
// 更新以索引为6(G)作为起点的与索引为6这个起点(G)连通的节点的路径距离
visited.updateDis(start);
// 由于起点已经排除,所以只需要判断剩下的节点即可
for (int i = 1; i < dijGraph.vertexData.length; i++) {
// 获取新的最短路径的节点为新的起点(因为一个节点走过了,那么下一个节点就成为新的起点)
int index = visited.getNewMinRoutVertex();
// 更新与新的起点连通的节点的距离,选择最小的作为连通点的距离
visited.updateDis(index);
}
// 展示从起始点G到每个节点的最短路径
visited.showVertexDis();
}
}
class Visited{
// 用于记录已访问的点
private int[] isVisitedVertex;
// 用于记录起始点到达该点的距离
private int[] vertexDis;
// 用于记录该点的前驱节点,记录前驱节点的作用就是可以遍历得到行走的轨迹
private int[] preVertex;
// NIF表示两点不可连通
private static final int NIF = 65535;
private DijGraph graph;
// 记录起始顶点索引
private int startVertexIndex;
/**
* 构造函数,对3个数组的初始化,以及接收图
* @param index 起始点的索引
* @param graph 图
*/
public Visited(int index, DijGraph graph) {
int len = graph.vertexData.length;
this.isVisitedVertex = new int[len];
isVisitedVertex[index] = 1; // 起始点设置为已访问,1表示已访问,0表示未被访问
this.vertexDis = new int[len];
Arrays.fill(vertexDis,NIF); // 初始距离都是NIF
vertexDis[index] = 0; // 起始点的距离,也就是G到G的距离就是0
this.preVertex = new int[len];
preVertex[index] = index; // 设置起始点的前驱节点的索引,就是他自己
this.startVertexIndex = index;
this.graph = graph;
}
/**
* 获得index索引节点的路径
* @param index
* @return
*/
public int getDis(int index){
return vertexDis[index];
}
/**
* 判断该索引的节点是否已被访问
* @param index
* @return
*/
public boolean getIsVisitedVertex(int index){
return isVisitedVertex[index]==1;
}
/**
* 更新每个节点到起始节点的路径长度
* @param index 当前起点
*/
public void updateDis(int index){
for (int j = 0; j < graph.weight[index].length; j++) {
// 类似广度优先搜索遍历,获取index这个节点与其连通的节点之间的距离加上index这个节点已经走过的距离
int len = getDis(index) + graph.weight[index][j];
// 如果len小于连通的这个节点已经存在的距离,那么说明走到j这个节点的距离最小的是len这个距离
// 并且j这个节点没有被访问过的话,就应该将这个节点的距离赋值为最小的len
// j这个节点的前驱节点就是index这个节点了
if (!getIsVisitedVertex(j) && len < getDis(j)){
vertexDis[j] = len;
preVertex[j] = index;
}
}
}
/**
* 获取没有访问过的节点中,已走的最短距离的那个节点的索引,
* 将其设置为已访问,并返回这个节点,作为新的起点
* @return
*/
public int getNewMinRoutVertex(){
int min = NIF;
int index = 0;
for (int i = 0; i < isVisitedVertex.length; i++) {
if (isVisitedVertex[i]==0 && vertexDis[i] < min){
min = vertexDis[i];
index = i;
}
}
isVisitedVertex[index] = 1;
return index;
}
// 遍历记录到达每个节点最短路径数组,以及记录的前驱节点数组
public void showVertexDis(){
for (int i = 0; i < vertexDis.length; i++) {
if (vertexDis[i]!=NIF){
System.out.println(graph.vertexData[i] + " : " +vertexDis[i]);
}else {
System.out.println("NIF");
}
}
System.out.println("具体行走路线:");
Set<Character> set = new LinkedHashSet<>();
Stack<Character> stack = new Stack<>();
for (int i = 0; i < preVertex.length; i++) {
int temp = i;
set.add(graph.vertexData[i]);
do{
temp = preVertex[temp];
set.add(graph.vertexData[temp]);
}while (preVertex[temp]!=startVertexIndex);
set.add(graph.vertexData[preVertex[temp]]);
for (Character character : set) {
stack.add(character);
}
while (!stack.isEmpty()){
System.out.print(stack.pop()+" ");
}
set.clear();
System.out.println(": "+vertexDis[i]);
}
}
}
class DijGraph{
char[] vertexData; // 存储图中顶点的数据
int[][] weight; // 邻接矩阵,即权路径
public DijGraph(char[] vertexData, int[][] weight) {
this.vertexData = new char[vertexData.length];
for (int i = 0; i < vertexData.length; i++) {
this.vertexData[i] = vertexData[i];
}
this.weight = new int[vertexData.length][vertexData.length];
for (int i = 0; i < weight.length; i++) {
for (int j = 0; j < weight[i].length; j++) {
this.weight[i][j] = weight[i][j];
}
}
}
}
结果:
A : 2
B : 3
C : 9
D : 10
E : 4
F : 6
G : 0
具体行走路线:
G A : 2
G B : 3
G A C : 9
G F D : 10
G E : 4
G F : 6
G : 0