图的最短路径问题
前面介绍了图的基本知识:数据结构 - 图与深度优先、广度优先遍历
现有一个图:有7个顶点{'A','B','C','D','E','F','G'}
,顶点间长度为权值
假设起点为G,现要求G点到各顶点的最短路径
迪杰斯特拉算法(Dijkstra)
Dijkstra算法以起始点为中心向外层层扩展(广度优先遍历),采用贪心算法的策略,每次遍历到始点距离最近且未访问过的顶点的邻接节点,直到扩展到终点为止
百度百科:
核心思路:
- 设置3个数组:alreadyArr记录以访问的顶点;preVisited记录各个顶点前驱结点;dis记录起点到各顶点的距离
- 从起点1开始广度优先遍历(每次遍历都更新3个数组,如顶点3的前驱结点为1,距离为9…后续遍历如果有更小值会覆盖);找到权值最小的边<1,2>=7,即下一个访问的顶点2,设置顶点2的前驱结点为1,距离为7
- 从顶点2开始广度遍历,重复上一步,到顶点3的距离为
7+10=17>9
,不更新preVisited和dis,选中顶点3,直到所有顶点都被访问,dis数组就是最终结果
Java代码实现Dijkstra算法
- 算法创建了3个对象:Graph图对象、VisitedVertex已访问顶点的集合,保存了3个数组;DijkstraAlgorithm主类
- VisitedVertex对象:初始化时需要设置dis自身为0,其余全为最大值(表示不连通),且设置起点被访问;updateArr方法为根据边权值选择下一访问点,访问alreadyArr集合与之对应的dis集合数据,找到最小值
- Graph图:update方法更新index下标顶点到周围顶点的距离dis数组和周围顶点的前驱顶点preVisited数组;dijkstra方法,从起点开始调用update方法直到所有顶点被访问
package com.company.十种算法.dijkstra;
import java.util.Arrays;
/**
* Author : zfk
* Data : 17:15
*/
public class DijkstraAlgorithm {
//用INF表示两个顶点不能连通
private static final int INF = 65535;
public static void main(String[] args) {
char[] vertex = new char[]{'A','B','C','D','E','F','G'};
int[][] matrix = {
{INF,5,7,INF,INF,INF,2},
{5,INF,INF,9,INF,INF,3},
{7,INF,INF,INF,8,INF,INF},
{INF,9,INF,INF,INF,4,INF},
{INF,INF,8,INF,INF,5,4},
{INF,INF,INF,4,5,INF,6},
{2,3,INF,INF,4,6,INF}
};
Graph graph = new Graph(vertex, matrix);
graph.showGraph();
graph.dijkstra(6);
graph.showDij();
}
}
class Graph{
//顶点数组
private char[] vertex;
//邻接矩阵
private int[][] matrix;
//表示已经访问的顶点的集合
private VisitedVertex visitedVertex;
public Graph(char[] vertex, int[][] matrix) {
this.vertex = vertex;
this.matrix = matrix;
}
public void showDij(){
visitedVertex.show();
}
public void showGraph(){
for (int[] link : matrix){
System.out.println(Arrays.toString(link));
}
}
/**
* dijkstra算法实现
* @param index 出发顶点对应的下标
*/
public void dijkstra(int index){
visitedVertex = new VisitedVertex(vertex.length, index);
//更新index顶点到周围顶点的距离和前驱
update(index);
for (int j = 1;j < vertex.length;j++){
//选择并返回新的访问结点
index = visitedVertex.updateArr();
//更新index顶点到周围顶点的距离和前驱
update(index);
}
}
//更新index下标顶点到周围顶点的距离和周围顶点的前驱顶点
private void update(int index){
int len = 0;
//根据遍历我们的邻接矩阵matrix
for (int j = 0;j < matrix[index].length;j++){
//len 表示出发顶点到index顶点距离+从index顶点到j顶点的距离和
len = visitedVertex.getDis(index) + matrix[index][j];
//如果j顶点没有被访问过并且len 小于出发顶点到j顶点的距离,就需要更新
if (!visitedVertex.in(j) && len < visitedVertex.getDis(j)){
//更新顶点j的前驱为index
visitedVertex.updatePre(j,index);
//更新出发顶点到j顶点的距离
visitedVertex.updateDis(j,len);
}
}
}
}
//已访问顶点的集合
class VisitedVertex{
//记录以访问的顶点 - 动态更新
public int[] alreadyArr;
//记录前驱结点,每个值为前一个顶点下标 - 动态更新
public int[] preVisited;
//记录出发顶点到其他所有顶点的距离,求的最短距离就会存在dis
public int[] dis;
/**
*
* @param length 顶点的格式
* @param index 出发顶点对应的下标
*/
public VisitedVertex(int length,int index) {
this.alreadyArr = new int[length];
this.preVisited = new int[length];
this.dis = new int[length];
//全部置为最大值
Arrays.fill(dis,65535);
//设置出发顶点被访问过
this.alreadyArr[index] = 1;
//自身置0
this.dis[index] = 0;
}
/**
* 判断index顶点是否被访问过
* @param index
* @return 访问过返回true,否则为false
*/
public boolean in(int index){
return alreadyArr[index] == 1;
}
/**
* 更新出发顶点到index顶点的距离
* @param index
* @param len
*/
public void updateDis(int index,int len){
dis[index] = len;
}
/**
* 更新顶点的前驱结点为index
* @param pre
* @param index
*/
public void updatePre(int pre,int index){
preVisited[pre] = index;
}
/**
* 返回出发顶点到index顶点的距离
* @param index
*/
public int getDis(int index){
return dis[index];
}
/**
* 继续选择并返回新的访问顶点,G点访问完后,以A点作为新的访问顶点
* @return
*/
public int updateArr(){
int min = 65535, index = 0;
for (int i = 0;i < alreadyArr.length;i++){
if (alreadyArr[i] == 0 && dis[i] < min){
min = dis[i];
index = i;
}
}
//更新index顶点被访问过
alreadyArr[index] = 1;
return index;
}
/**
* 显示最后的结果,即将三个数组的情况输出
*/
public void show(){
System.out.println("==============已访问的顶点===============");
//输出alreadyArr
for (int i : alreadyArr){
System.out.print(i+ " ");
}
System.out.println();
System.out.println("===============前驱顶点==============");
//输出preVisited
for (int i : preVisited){
System.out.print(i+ " ");
}
System.out.println();
System.out.println("==============顶点间最短距离===============");
//输出dis
for (int i : dis){
System.out.print(i+ " ");
}
System.out.println();
char[] vertex = new char[]{'A','B','C','D','E','F','G'};
int count = 0;
for (int i : dis){
if (i != 65535){
System.out.print(vertex[count] + "( " + i + " )");
}else {
System.out.println(" N " );
}
count++;
}
}
}
最终结果:
弗洛伊德算法(Floyd)
同为最短路径问题的算法,floyd算法是得到所有顶点到其他顶点的最短路径(当然Dijkstra也可以)
floyd算法的核心思想是三层for循环与
状态转移方程dis[i][j] = min{dis[i][k] + dis[k][j],dis[i][j]}
即找到权值最小边
设置好两个二维数组:dis保存各顶点出发到其他顶点的距离;pre保存顶点的前驱结点
初始化时dis数组就是图的邻接矩阵,设置结点的前驱结点为自身
INF表示结点间不连通,顶点到自身的距离为0
int[][] matrix = {
{0,5,7,INF,INF,INF,2},
{5,0,INF,9,INF,INF,3},
{7,INF,0,INF,8,INF,INF},
{INF,9,INF,0,INF,4,INF},
{INF,INF,8,INF,0,5,4},
{INF,INF,INF,4,5,0,6},
{2,3,INF,INF,4,6,0}
};
查找最短路径一般都是直连与间接连通的长度比较:min{dis[i][k] + dis[k][j],dis[i][j]},如AB的最短距离是<A,B>还是<A,G>+<G,B>呢?
所有我们引入中间结点,例如,以A为中间结点,所有经过A点的两顶点间的最短路径就是min{dis[i][0] + dis[0][j],dis[i][j]}
- 起点为A,终点为
{'A','B','C','D','E','F','G'}
与A连通的结点B、C、G,更新dis数组和pre数组;
不连通的设置一个最大值INF表示即可
- 起点为B,终点为
{'A','B','C','D','E','F','G'}]
实际上都大于以A为起点的距离,所以不用更新dis数组和pre数组
-
继续循环起点为
{C,D,E,F,G}
,找到所有经过顶点A的两顶点的最短路径 -
然后就是循环中间结点
{'A','B','C','D','E','F','G'}
,类似与穷举法,找出了所有可能,最终dis就是所有顶点到其他顶点的最短距离
Java代码实现floyd算法
floyd算法重点就在与三个for循环
package com.company.十种算法.floyd;
import java.util.Arrays;
/**
* Author : zfk
* Data : 16:51
* 弗洛伊德算法 : 最短路径问题(所有结点)
*/
public class FloydAlgorithm {
//用INF表示两个顶点不能连通
private static final int INF = 65535;
public static void main(String[] args) {
char[] vertex = new char[]{'A','B','C','D','E','F','G'};
int[][] matrix = {
{0,5,7,INF,INF,INF,2},
{5,0,INF,9,INF,INF,3},
{7,INF,0,INF,8,INF,INF},
{INF,9,INF,0,INF,4,INF},
{INF,INF,8,INF,0,5,4},
{INF,INF,INF,4,5,0,6},
{2,3,INF,INF,4,6,0}
};
Graph graph = new Graph(matrix, vertex);
graph.floyd();
graph.show();
}
}
//图对象
class Graph{
//图中顶点数组
private char[] vertex;
//保存各顶点出发到其他顶点的距离,最后结果也是保存在该数组
private int[][] dis;
//保存到达目标顶点的前驱结点
private int[][] pre;
/**
* 构造器
* @param matrix 邻接矩阵
* @param vertex 顶点数组
*/
public Graph(int[][] matrix,char[] vertex){
int length = vertex.length;
this.vertex = vertex;
this.dis = matrix;
this.pre = new int[length][length];
//对pre初始化,最开始时各顶点的前驱为自身
// 且该数组存放的是顶点的下标 'A' - 0
for (int i =0;i < length;i++){
Arrays.fill(pre[i],i);
}
}
//显示pre数组合dis数组
public void show(){
System.out.println("==========pre数组============");
for (int k = 0;k < dis.length;k++){
for (int j = 0;j < dis.length;j++){
System.out.print(vertex[pre[k][j]] + " ");
}
System.out.println();
}
System.out.println("==========dis数组============");
for (int k = 0;k < dis.length;k++){
for (int j = 0;j < dis.length;j++){
System.out.printf("%2d\t",dis[k][j]);
}
System.out.println();
}
}
//弗洛伊德算法
public void floyd(){
//两顶点的距离
int len = 0;
int length = dis.length;
//对中间顶点数组变量,k为中间顶点数组的下标,{'A','B','C','D','E','F','G'}
for (int k = 0;k < length;k++){
//从i顶点出发,{'A','B','C','D','E','F','G'}
for (int i = 0;i < length;i++){
//j顶点为终点,{'A','B','C','D','E','F','G'}
for (int j = 0;j < length;j++){
//从i顶点出发经过k中间顶点,到达j顶点的距离
len = dis[i][k] + dis[k][j];
//len小于两顶点直连的距离,就更新
if (len < dis[i][j]){
dis[i][j] = len;
//设置前驱结点
pre[i][j] = pre[k][j];
}
}
}
}
}
}