最短路径算法:用于计算一个节点到其他所有节点的最短路径。是图论研究中的一个经典算法问题。
一、Dijkstra算法:
典型的最短路径路由算法,用于计算一个节点到其他所有节点的最短路径。主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。Dijkstra算法能得出最短路径的最优解,但由于遍历计算的节点很多,所以效率低。可以用堆优化。
按照路径长度递增的次序一步步并入来求取,是贪心算法的一个应用,用来解决单源点到其余顶点的最短路径问题。使用了广度优先搜索BFS解决赋权有向图或者无向图的单源最短路径问题,算法最终得到了一个最短路径树。
算法思想:
两个集合P、Q,P中存放已经确定最短路径的结点,Q中存放未确定的;两个数组dis[]、t[],dis中用于存放从起点到该结点的最短路径值,t用于标识该节点是否在集合P中,在为1,否则为0;
1)首先将起点加入集合P中,其余节点放在Q中,起点的dis为0,其余节点为无穷大。
2)遍历一次获取起点v到各个节点的距离,更新dis;
3)得到距离最短的结点x,将其加入P中,表示从起点v到x的最短距离已经确定;
4)计算该结点到其余节点的距离,判断dis[i]与dis[x]+(x, i)的距离,如果距离更小则更新dis值;这叫做松弛。
5)更新dis后,选出最小的dis,将该结点加入P集合,在一次重复以上步骤,知道Q集合中的所有结点的最短路径值都已经得到,则算法结束。
实现:
1、基于邻接矩阵
public class Dijkstra {
// 图的表示
int numOfVexs;//节点数
int[][] edges;//邻接矩阵,表示节点间距离,不可达距离为无穷大
public int[] dijkstra(int v){
if(v < 0||v >= numOfVexs)
return null;
boolean[] st = new boolean[numOfVexs];//默认初始为false,判断该节点是否已经计算过最短距离
int[] distance = new int[numOfVexs];//存放源点到其他点距离
//设置不可达距离为无穷大,这里为一个极大值
for(int i = 0; i < numOfVexs; i++){
for(int j = i+1; j < numOfVexs; j++){
if(edges[i][j] == 0){//表示两个点之间不可达
edges[i][j] = Integer.MAX_VALUE;
edges[j][i] = Integer.MAX_VALUE;
}
}
}
//更新起点到各个节点的距离
for(int i = 0; i < numOfVexs; i++){
distance[i] = edges[v][i];
}
//表明该节点已经计算过最短距离
st[v] = true;
//处理从起点到其余节点的距离
for(int i = 0; i < numOfVexs; i++){
int min = Integer.MAX_VALUE;
int index = -1;
//比较起点到其余顶点的路径长度
for(int j = 0; j < numOfVexs; j++){
// 从起点到当前节点的最短路径还未找到
if(st[j] == false){
if(distance[j] < min){
index = j;
min = distance[j];
}
}
}
// 找到源点到索引index顶点的最短路径长度
if(index != -1){
st[index] = true;
}
// 更新当前最短路径及距离
for(int w = 0; w < numOfVexs; w++){
if(st[w] == false){
if(edges[index][w] != Integer.MAX_VALUE&&(min+edges[index][w] < distance[w])){
distance[w] = min+edges[index][w];
}
}
}
}
return distance;
}
}
2、基于邻接表
public class Dijkstra {
//邻接表中表对应的链表的顶点
private class ENode{
ENode nextadj;//指向下一个相邻接点
int adjvex;//该边所指向的顶点的位置
int weight;//连接两个顶点的边的权值
}
//邻接表中表的顶点
private class VNode{
ENode firstadj;//指向第一条依附在该顶点的弧
}
private int numOfVexs;//节点数
private VNode[] vexs;//顶点数组
public int[] dijkstra2(int v) {
if (v < 0 || v >= numOfVexs)
return null;
boolean[] st = new boolean[numOfVexs];// 默认初始为false
int[] distance = new int[numOfVexs];// 存放源点到其他点的距离
for (int i = 0; i < numOfVexs; i++) {
distance[i] = Integer.MAX_VALUE;
}
ENode current;
current = vexs[v].firstadj;
while (current != null) {
distance[current.adjvex] = current.weight;
current = current.nextadj;
}
distance[v] = 0;
st[v] = true;
// 处理从源点到其余顶点的最短路径
for (int i = 0; i < numOfVexs; i++) {
int min = Integer.MAX_VALUE;
int index = -1;
// 比较从源点到其余顶点的路径长度
for (int j = 0; j < numOfVexs; j++) {
// 从源点到j顶点的最短路径还没有找到
if (st[j] == false) {
// 从源点到j顶点的路径长度最小
if (distance[j] < min) {
index = j;
min = distance[j];
}
}
}
// 找到源点到索引为index顶点的最短路径长度
if (index != -1)
st[index] = true;
// 更新当前最短路径及距离
for (int w = 0; w < numOfVexs; w++)
if (st[w] == false) {
current = vexs[w].firstadj;
while (current != null) {
if (current.adjvex == index)
if ((min + current.weight) < distance[w]) {
distance[w] = min + current.weight;
break;
}
current = current.nextadj;
}
}
}
return distance;
}
}
二、Floyd算法
又称为插点法,是一种用动态规划的思想寻找给定的加权图中多源点之间最短路径的算法。我们可以求出任意两个点之间最短路径。时间复杂度为O(n^3)。Floyd-Warshall算法不能解决带有“负权回路”的图。
算法特点:Floyd是解决任意两点间的最短路径的一种算法,可以正确处理有向图或无向图或负权的最短路径问题(但不能存在负权回路),同时也被用于计算有向图的传递闭包。
算法思路:
通过Floyd计算图G =(V,E)中各个顶点的最短路径时,需要引入两个矩阵,矩阵D中的元素d[i][j]表示顶点i(第i个顶点)到顶点j(第j个顶点)的距离。矩阵P中的元素p[i][j],表示顶点i到顶点j经过了p[i][j]记录的值所表示的顶点,也就是说i到j需要经过中间点p[i][j]。
假设图G中顶点个数为N,则需要对矩阵D和矩阵P进行N次更新。初始时,矩阵D中顶点d[i][j]的距离为顶点i到顶点j的权值;如果i和j不相邻,则d[i][j]=∞,矩阵P的值为顶点p[i][j]的j的值。
接下来开始,对矩阵D进行N次更新。
1)第1次更新时,如果”d[i][j]的距离” > “d[i][0]+d[0][j]”(d[i][0]+d[0][j]表示”i与j之间经过第1个顶点的距离”),则更新d[i][j]为”d[i][0]+d[0][j]”,更新p[i][j]=p[i][0],也就是将0这个节点存入。
2)同理,第k次更新时,如果”d[i][j]的 距离” > “d[i][k-1]+d[k-1][j]”,则更新d[i][j]为”d[i][k-1]+d[k-1][j]”,p[i][j]=p[i][k-1]。更新N次之后,操作完成。
算法实现:
public class Floyd {
private int[][] matrix;//初始的邻接矩阵(未更新前的矩阵)
private char[] nodes;//节点集合
static int INF=Integer.MAX_VALUE;
public Floyd(int[][] matrix,char[] nodes){
this.matrix=matrix;
this.nodes=nodes;
}
public void floydFunction(int[][] distance,int[][] paths){
//初始化distance[][],paths[][]
for (int i=0;i<nodes.length;i++){
for (int j=0;j<nodes.length;j++){
distance[i][j]=matrix[i][j];//"顶点i"到"顶点j"的路径长度为"i到j的权值"。
paths[i][j]=j;//"顶点i"到"顶点j"的最短路径是经过顶点j。
}
}
System.out.printf("Floyd算法执行前 distance: \n");
print(distance);
System.out.printf("Floyd算法执行前 paths: \n");
print(paths);
//循环更新distance[][],paths[][]
for (int k=0;k<nodes.length;k++){
for (int i=0;i<nodes.length;i++){
for (int j=0;j<nodes.length;j++){
int temp=(distance[i][k]==INF||distance[k][j]==INF)?INF:(distance[i][k]+distance[k][j]);
if(distance[i][j]>temp){
"i到j最短路径"对应的值设,为更小的一个(即经过k)
distance[i][j]=Math.min(temp,distance[i][j]);
// "i到j最短路径"对应的路径,经过k
paths[i][j]=k;
}
}
}
}
System.out.printf("Floyd算法执行后 distance: \n");
print(distance);
System.out.printf("Floyd算法执行后 paths: \n");
print(paths);
}
// 打印floyd最短路径的结果
public void print(int[][] distance){
for (int i = 0; i < distance.length; i++) {
for (int j = 0; j < distance.length; j++)
System.out.printf("%2d ", distance[i][j]);
System.out.printf("\n");
}
}
public static void main(String[] args){
int[][] matrix={{0,5,INF},{INF,0,6},{7,9,0}};
char[] nodes={'a','b','c'};
Floyd floyd=new Floyd(matrix,nodes);
//长度数组。即,dist[i][j]=sum表示,"顶点i"到"顶点j"的最短路径的长度是sum。
int[][] distance=new int[nodes.length][nodes.length];
//路径。path[i][j]=k表示,"顶点i"到"顶点j"的最短路径会经过顶点k。
int[][] paths=new int[nodes.length][nodes.length];
floyd.floydFunction(distance,paths);
}
}