1.朴素Dijkstra算法
基本步骤:
1.定义一个数组dist表示源点到其余各个结点的距离,dist[i]表示源点到结点i的距离,数组dist内各个元素初始为无穷大
用一个状态数组state标记是否找到源点到该结点的最短距离,如果为真,则表示找到了源点到该结点的最短距离,如果为假,则表示还没有找到源点到该点的最短距离 状态数组state初始为假
2.源点到源点的距离为0 则dist[1]=0
3.遍历dist数组,找到一个未确定为最短路径的所有结点中离源点最近的结点。假设该结点编号为i,则更新对应的state数组:state[i]=true
4.遍历i所有可能到达的结点。如果dist[j]大于dist[i]加上i到j的距离,即dist[j]>dist[i]+w[i][j],更新dist[j]=dist[i]+w[i][j]
5.重复 3 4 直至所有的结点的状态更新成true
6.此时dist数组中保存了源点到各结点的最短路径
import java.util.*;
public class Main{
static int N=510;
static int n,m;
static int max=0x3f3f3f3f;//定义max为无穷大 10^9
static int[][] g=new int[N][N];//存两个点之间的距离
static int[] dist=new int[N];//存每个点到起点之间的距离
static boolean[] st=new boolean[N];//状态数组 标记源点到该结点是否已经是最短路径
public static int dijkstra(){
//初始化 将dist数组全部赋最大值
Arrays.fill(dist,max);
dist[1]=0;//源点到源点的距离为0
//遍历dist数组
for(int i=0;i<n;i++){
int t=-1;//临时变量
//遍历所有未标记为最短路径的结点中dist最小的点
for(int j=1;j<=n;j++){
if(!st[j]&&(t==-1||dist[j]<dist[t]))
t=j;
}
//找到了距离源点最近的点 并更新它的state
st[t]=true;
//遍历所有与i结点相连的结点 更新最短路径
for(int j=1;j<=n;j++){
dist[j]=Math.min(dist[j],dist[t]+g[t][j]);
}
}
//源点到n的长度没有改变 输出-1 若改变 则输出最短路径长度
if(dist[n]==max) return -1;
else return dist[n];
}
public static void main(String[] args){
Scanner sc=new Scanner(System.in);
n=sc.nextInt();
m=sc.nextInt();
for(int i=1;i<=n;i++){
Arrays.fill(g[i],max);
}
while(m-->0){
int a=sc.nextInt();
int b=sc.nextInt();
int c=sc.nextInt();
//如果出现重边 更新边为较小值
g[a][b]=Math.min(g[a][b],c);
}
int res=dijkstra();
System.out.println(res);
}
}
2.堆优化的Dijkstra算法
优化细节见AcWing 850. Dijkstra求最短路 II - AcWing
import java.io.*;
import java.util.*;
public class Main{
static int n,m,idx;
static int N=150010;
static int max=0x3f3f3f3f;
static int[] h=new int[N];
static int[] w=new int[N];//存取a到b之间的距离
static int[] e=new int[N];
static int[] ne=new int[N];
static int[] dist=new int[N];
static boolean[] st=new boolean[N];
public static void add(int a,int b,int c){
e[idx]=b;
w[idx]=c;
ne[idx]=h[a];
h[a]=idx++;
}
public static int dijkstra(){
//定义一个优先级队列
PriorityQueue<Pair> queue=new PriorityQueue<Pair>();
Arrays.fill(dist,max);
dist[1]=0;
//将初始结点入队
queue.add(new Pair(0,1));
while(!queue.isEmpty()){
//队列非空 取出非空结点
Pair p=queue.poll();
int t=p.getSecond();
int distance=p.getFirst();
if(st[t]) continue;
st[t]=true;
//遍历所有与当前结点相邻的结点 并更新它们对应的的dist 更新后加入队列
for(int i=h[t];i!=-1;i=ne[i]){
int j=e[i];
if(dist[j]>distance+w[i]){
dist[j]=distance+w[i];
queue.add(new Pair(dist[j],j));
}
}
}
if(dist[n]==max) return -1;
else return dist[n];
}
public static void main(String[] args)throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String[] s=br.readLine().split(" ");
n=Integer.parseInt(s[0]);
m=Integer.parseInt(s[1]);
Arrays.fill(h,-1);
while(m-->0){
String[] str=br.readLine().split(" ");
int a=Integer.parseInt(str[0]);
int b=Integer.parseInt(str[1]);
int c=Integer.parseInt(str[2]);
add(a,b,c);
}
int res=dijkstra();
System.out.println(res);
}
}
//定义一个类pair用来存储位置以及源点到它的最短距离
class Pair implements Comparable<Pair>{
private int first;
private int second;
public int getFirst(){
return this.first;
}
public int getSecond(){
return this.second;
}
public Pair(int first,int second){
this.first=first;
this.second=second;
}
@Override
public int compareTo(Pair o){
return Integer.compare(first,o.first);
}
}
时间复杂度O(mlogn)
补充:优先级队列相关Java【优先级队列】详细图解 / 模拟实现 + 【PriorityQueue】常用方法介绍_java优先级队列_灵魂相契的树的博客-CSDN博客
3.Bellman-Ford算法
假设 1 号点到 n 号点是可达的,每一个点同时向指向的方向出发,更新相邻的点的最短距离,通过循环 n-1 次操作,若图中不存在负环,则 1 号点一定会到达 n 号点,若图中存在负环,则在 n-1 次松弛后一定还会更新
基本步骤:
for n次
for 所有边 a,b,w (松弛操作)
dist[b] = min(dist[b],backup[a] + w)
注意:backup[] 数组是上一次迭代后 dist[] 数组的备份,由于是每个点同时向外出发,因此需要对 dist[] 数组进行备份,若不进行备份会因此发生串联效应,影响到下一个点
题解
import java.util.*;
import java.io.*;
public class Main{
static int n,m,k;
static int N=510,M=10010;
static int max=0x3f3f3f3f;
static Node[] list=new Node[M];//定义数组存储每个三元组
static int[] dist=new int[N];
static int[] back=new int[N];
public static void bellman_ford(){
//dist数组初始赋最大值
Arrays.fill(dist,max);
dist[1]=0;
for(int i=0;i<k;i++){
back=Arrays.copyOf(dist,n+1);//数组备份 防止串连
for(int j=0;j<m;j++){
Node edg=list[j];
int a=edg.a;
int b=edg.b;
int c=edg.c;
dist[b]=Math.min(dist[b],back[a]+c);//更新dist数组
}
}
//这里dist[n]需要和max/2进行对比 防止因为负权边而导致dist[n]不等于max 但仍为无穷大
if(dist[n]>max/2) System.out.println("impossible");
else System.out.println(dist[n]);
}
public static void main(String[] args)throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String[] s=br.readLine().split(" ");
n=Integer.parseInt(s[0]);
m=Integer.parseInt(s[1]);
k=Integer.parseInt(s[2]);
for(int i=0;i<m;i++){
String[] str=br.readLine().split(" ");
int a=Integer.parseInt(str[0]);
int b=Integer.parseInt(str[1]);
int c=Integer.parseInt(str[2]);
list[i]=new Node(a,b,c);
}
bellman_ford();
}
}
//定义一个类用来存储三元组 从a到b 权重为c
class Node{
int a,b,c;
public Node(int a,int b,int c){
this.a=a;
this.b=b;
this.c=c;
}
}
时间复杂度O(nm)
4.SPFA算法
题意描述同Bellman-Ford算法
题解
此处的数组st用于表示当前位置是否在队列当中 如果是为true 否为false
import java.io.*;
import java.util.*;
public class Main{
static int n,m,idx,hh,tt;
static int N=100010;
static int max=0x3f3f3f3f;
static int[] q=new int[N];
static int[] h=new int[N];
static int[] w=new int[N];
static int[] e=new int[N];
static int[] ne=new int[N];
static int[] dist=new int[N];
static boolean[] st=new boolean[N];
public static void add(int a,int b,int c){
e[idx]=b;
w[idx]=c;
ne[idx]=h[a];
h[a]=idx++;
}
public static void spfa(){
hh=0;
tt=-1;
Arrays.fill(dist,max);
dist[1]=0;
q[++tt]=1;//更新位置1上的结点信息并入队
st[1]=true;
while(hh<=tt){
int t=q[hh++];//取出第一个队列第一个结点
st[t]=false;
for(int i=h[t];i!=-1;i=ne[i]){
int j=e[i];
if(dist[j]>dist[t]+w[i]){
dist[j]=dist[t]+w[i];//更新最短路径长度
if(!st[j]){//如果此时的j并不在队列中 入队
q[++tt]=j;
st[j]=true;
}
}
}
}
if(dist[n]==max) System.out.println("impossible");
else System.out.println(dist[n]);
}
public static void main(String[] args)throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String[] s=br.readLine().split(" ");
n=Integer.parseInt(s[0]);
m=Integer.parseInt(s[1]);
Arrays.fill(h,-1);
while(m-->0){
String[] str=br.readLine().split(" ");
int a=Integer.parseInt(str[0]);
int b=Integer.parseInt(str[1]);
int c=Integer.parseInt(str[2]);
add(a,b,c);
}
spfa();
}
}
时间复杂度O(m) 最坏为O(nm)
如何判断是否有负环?
开辟一个数组cnt 记录从结点a到b经过的边数 如果边数大于等于n 则表示有环
需要调整初始状态 初始将所有的结点都加入队列 环未必在1到n的路上