迪杰斯特拉算法介绍:
杰斯特拉算法是由荷兰计算机科学家狄克斯特拉于1959 年提出的,因此又叫狄克斯特拉算法。是从一个顶点到其余各顶点的最短路径算法,解决的是有向图中最短路径问题。迪杰斯特拉算法主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。
算法思想:
- 对于一个图G,计算最短路径时候,需要指定起点v0(从起始点开始计算)
- 把点划分为两组:
1)第一组S:为已经求出的最短路径的终点集合(初始时只包含源点v0)
2)第二组V - S: 为尚未求出的最短路劲的顶点集合(初始时为V - {v0}) - 算法将按顶点与v0间最短路径长度递增的次序,逐个将集合V - S中的顶点加入到集合S中去。在这个过程中,总保持从v0到集合S中各顶点的路径长度始终不大于到集合V - S中的各顶点的路径长度。
利用反证法,可以证明此算法:
假设此路径上有一个顶点不在S中,则说明存在一条终点不在S而长度比此路径短的路径。但是这是不可能的。因为算法是按照路径长度递增的次序来产生最短路径的,故长度比此路径的所有路径均已产生,它们的终点必定在S中,即假设不成立。
算法实现思路:
- 图,这里我用带权的邻接矩阵(matrix)表示(若边不存在,则权值为INF)
- 用一个一维数组(isMinDist),记录从源点v0到终点vi是否已被确定最短路径长度,true表示确定,false表示尚未确定。
- 一维数组(prefix)记录从源点v0到终点vi的当前最短路径上vi的直接前驱顶点序号。其初值为本身
- 一维数组(mindist):记录从源点v0到终点vi的当前最短路径长度。其初值为:如果从v0到vi有弧,则mindist[i] 为弧上的权值,否则为INF。
- 从V - S中选出"距离最短的顶点k",并将顶点k加入到S中;同时,从V - S中移除顶点k。
- 更新V - S中各个顶点到起点v0的距离。之所以更新V - S 中顶点的距离,是由于上一步中确定了k是求出最短路径的顶点,从而可以利用k来更新其它顶点的距离;例如,(v0,vi)的距离可能大于(v0,vk)+(vk,vi)的距离。
- 重复步骤5 和 6
前期代码准备:
import java.io.IOException;
import java.util.Scanner;
public class Dijkstra {
private char[] vertex; //顶点集合
private int[][] matrix; //邻接矩阵
private static final int INF = 999999; //最大值
/**
* 创建图
*/
public Dijkstra() {
Scanner sca = new Scanner(System.in);
int vertexNum = sca.nextInt(); //顶点数
int matrixNum = sca.nextInt(); //边数
vertex = new char[vertexNum];
vertex = sca.next().toCharArray(); //初始化顶点
//初始化矩阵
matrix = new int [vertexNum][vertexNum];
for(int i = 0; i < vertexNum; i++)
for(int j = 0; j < vertexNum; j++)
matrix[i][j] = (i == j) ? 0 : INF;
for(int i = 0; i < matrixNum; i++) { //初始化边的权值
char start = readChar(); //边的起点
char end = readChar(); //边的终点
int weight = sca.nextInt(); //边的权值
int startInedx = getLocation(start); //获取边的起点坐标
int endIndex = getLocation(end); //获取边的终点坐标
if(startInedx == -1 || endIndex == -1) return;
matrix[startInedx][endIndex] = weight;
matrix[endIndex][startInedx] = weight;
}
sca.close();
}
/**
* 读取一个输入字符
* @return
*/
private char readChar() {
char ch = '0';
do {
try {
ch = (char)System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}while(!((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')));
return ch;
}
/**
* 返回字符的位置
*/
private int getLocation(char c) {
for(int i = 0; i < vertex.length; i++)
if(vertex[i] == c) return i;
return -1;
}
public static void main(String[] args) {
Dijkstra dij = new Dijkstra();
dij.dijkstra(0);
}
}
迪杰斯特拉核心算法:
public void dijkstra(int start) {
int num = matrix[0].length;
//前驱节点
int[] prefix = new int[num];
//最短距离组
int[] mindist = new int[num];
//该节点是否已经找到最短路径
boolean[] isMinDist = new boolean[num];
int snear = 0;
//初始化前驱顶点和第一个顶点到其他顶点的最短路径
for(int i = 0; i < num; i++) {
prefix[i] = i; //刚开始前驱节点是本身
mindist[i] = matrix[start][i]; //刚开始,第一个顶点到每个顶点的最短距离就是对应权值
isMinDist[i] = false;
}
isMinDist[start] = true; //第一个顶点,已经找到最短路径,更改状态
for(int i = 1; i < num; i++) {
//每次循环求得距离start最近的顶点snear和最短距离min
int min = INF;
for(int j = 0; j < num; j++) {
if(!isMinDist[j] && mindist[j] < min) {
min = mindist[j];
snear = j;
}
}
isMinDist[snear] = true; //知道start到此顶点的最短距离
//根据snear修正start到其他所有节点的前驱节点及距离
for(int k = 0; k < num; k++) {
if(!isMinDist[k] && ((min + matrix[snear][k]) < mindist[k])) {
prefix[k] = snear;
mindist[k] = min + matrix[snear][k];
}
}
}
//打印寻找最短路径
for(int i = 0; i < num; i++)
System.out.println(vertex[start] + "——>" + vertex[i] + ": s = " + mindist[i]);
}
弗洛伊德算法介绍:
Floyd算法又称为插点法,是一种利用动态规划的思想寻找给定的加权图中多源点之间最短路径的算法,与Dijkstra算法类似。该算法名称以创始人之一、1978年图灵奖获得者、斯坦福大学计算机科学系教授罗伯特·弗洛伊德命名。
算法思想:
给定一个邻接矩阵,每次加入一个顶点,这个点作为中转点,分别求到图中其他顶点的路径,如果路径和原来比,变短,更新dist,path(记录中转点)。
算法实现思路:
- 此处我用带权的邻接矩阵代表图
- 二维数组(path)path[i][j] = k ——>顶点 ‘i’ 到顶点 'j’的最短路径会经过顶点k
- 二维数组(dist) dist[i][j] = sum ——> 顶点 ‘i’ 到顶点 'j’的最短路径的长度
弗洛伊德核心算法
/**
* Floyd最短路径
* 统计图中各个顶点间的最短路径
* path: path[i][j] = k ——>顶点 'i' 到顶点 'j'的最短路径会经过顶点k
* dist: dist[i][j] = sum ——> 顶点 'i' 到顶点 'j'的最短路径的长度
*/
public void floyd(int[][] path, int[][] dist) {
int len = vertex.length;
//初始化
for(int i = 0; i < len; i++) {
for(int j = 0; j < len; j++) {
dist[i][j] = matrix[i][j]; //顶点 'i' 到 顶点 'j' 的路径长度为 'i' 到 'j'的权值
path[i][j] = j; //顶点 'i' 到顶点 'j' 的最短路径是经过顶点j
}
}
//计算最短路径
for(int i = 0; i < len; i++) {
for(int j = 0; j < len; j++) {
for(int k = 0; k < len; k++) {
//如果经过下标为k顶点路径比原两点间路径更短,则更新dist[j][k] 和 path[j][k]
int temp = (dist[j][i] == INF || dist[i][k] == INF) ? INF : (dist[j][i] + dist[i][k]);
if(dist[j][k] > temp) {
// 'j' 到 'k'最短路径 对应的值,为更小的一个(经过k)
dist[j][k] = temp;
// 'j' 到 'k'的最短路径对应的路径,经过i,即标记前驱是哪个点
path[j][k] = path[j][i];
}
}
}
}
//打印Floyd最短路径的结果
System.out.println("Floyd:");
for(int i = 0; i < len; i++) {
for(int j = 0; j < len; j++)
System.out.print(dist[i][j] + "\t");
System.out.println();
}
}
注:
两个核心算法代码,可以直接放到第一个前期准备代码里,直接运行,就可以。
迪杰斯特拉测试数据:
输入:
4 8
ABCD
A B 1
A D 4
B C 9
B D 2
C A 3
C B 5
C D 8
D C 6
输出:
A——>A: s = 0
A——>B: s = 1
A——>C: s = 9
A——>D: s = 3
弗洛伊德测试数据:
输入:
4 8
ABCD
A B 1
A D 4
B C 9
B D 2
C A 3
C B 5
C D 8
D C 6
输出: