在图论中的一个经典求最短路的算法,基本上,所有的数据结构的书籍都会有这个算法,这算法有一个特性,我要求一个点到另一个点的最短距离,这个算法不仅给出这个最短距离还有很多“副产品”,也就是说只要在图中所有与该点连通的点,那么这些点的最短路会一并求出。
适用条件:
1.所有所有权边均为正。
2.有向/无向。
这里我们用邻接表来描述(存储)图。设点为1到n编号的店,m条边,与之对应的m个权值,x为起点(这里只考虑简单图)
Dijkstra算法的java实现
import java.util.ArrayList;
import java.util.Scanner;
public class Main
{
static ArrayList<Edge> list[];
static int d[];//存储某个点到所有点的距离,若两个点不联通默认为最大值
static int mark[];//访问标记
static int n;//n个点
static int m;//m条边
public static void main(String[] args)
{
Scanner sc=new Scanner(System.in);
n=sc.nextInt();
m=sc.nextInt();
int x=sc.nextInt();//起点
d=new int[n+1];
mark=new int[n+1];
list=(ArrayList<Edge>[])new ArrayList[n+1];
for(int i=0;i<m;i++)
{
int x=sc.nextInt();
int y=sc.nextInt();
int w=sc.nextInt();
if(list[x]==null)list[x]=new ArrayList<>();
if(list[y]==null)list[y]=new ArrayList<>();
list[x].add(new Edge(y,w));
list[y].add(new Edge(x,w));//图的构建,假设为无向图
}
Dijkstra(x);
for(int i=1;i<=n;i++)
{
if(d[i]!=Integer.MAX_VALUE)//输出所有可达路径的最短路
{
System.out.println(x+"->"+i+":"+d[i]);
}
}
}
void Dijkstra(int x)//求从x这个点出发,到所有点的最短路
{
for(int i=1;i<=n;i++)
{
d[i]=Integer.MAX_VALUE;//初始化,所有点均为最大值
}
d[x]=0;//定义起点到起点的距离为零
for(int i=1;i<=n;i++)
{
int now=0,min=Integer.MAX_VALUE;
for(int j=1;j<=n;j++)
if(mark[j]!=1&&d[j]<min)min=d[now=j];//寻找之前没有考虑过且到原点距离最小的点,显然当第一次进入循环,必定是从x开始考虑
mark[now]=1;//此时标记
for(int j=0;j<list[now].size();j++)//找到所有与now邻接的边
{
int to=list[now].get(j).to;
d[to]=Math.min(d[to], d[now]+list[now].get(j).w);//一直在更新最小值
}
}
}
}
class Edge
{
int to;//连到哪个边
int w;//边的权值
Edge(int to,int w)
{
this.to=to;
this.w=w;
}
}
如果题目还要求 最短路的路径,那么我们需要一个栈,假设从x到j点,
从尾端j开始考虑,用的d[j]=d[k]+w(j,k)(k为其中一个与j邻接的店)来判断k是否为j的上一个路径的点,当然这是针对无向图来说,有向图不适用
下面给出代码
static void findLast(int x,int begin)//递归调用
{
if(begin==x)//如果当前点和起点相同就结束
{
stack.push(x);
return;
}
for(int i=0;i<list[x].size();i++)
{
int k=list[x].get(i).to;
int w=list[x].get(i).w;
if(d[x]==d[k]+w)//判断式
{
stack.push(x);
findLast(k,begin);
return;//这个是必要的,因为最短路的个数可能不只一个,即值相同,路径不相同,如果题目说明只有一个最短路,这个return可不加,
}
}
}
或者 之前的代码
d[to]=Math.min(d[to], d[now]+list[now].get(j).w);
替代为
if(d[to] > d[now] + list[now].get(j).w)
{
d[to] = d[now] + list[now].get(j).w;
pre[to] = now;//设置一个数组pre(previous)来记录上一个节点
}
这段代码的官方名称叫松弛操作(relaxation)。
优化:
for(int i=1;i<=n;i++)
{
int now=0,min=Integer.MAX_VALUE;
for(int j=1;j<=n;j++)
if(mark[j]!=1&&d[j]<min)min=d[now=j];
mark[now]=1;
for(int j=0;j<list[now].size();j++)
{
int to=list[now].get(j).to;
d[to]=Math.min(d[to], d[now]+list[now].get(j).w);
}
}
以上这段代码其实可以进一步优化一下,我们采用优先队列,来实现找出之前没有考虑过且到原点距离最小的点。为了使用队列,我们还需定义一个数据结构Pair(记录当前点的暂时的最短路)如下:
PriorityQueue<Pair> queue=new PriorityQueue<>();
queue.add(new Pair(x,0));//加入第一个点
while(!queue.isEmpty())
{
Pair p=queue.poll();//出队列
if(mark[p.x]==1)continue;//如果之前考虑过就不再考虑
mark[p.x]=1;//然后标志当前
for(int j=0;j<list[p.x].size();j++)
{
int to=list[p.x].get(j).to;//其中一个与p.x邻接的店
d[to]=Math.min(d[to], d[p.x]+list[p.x].get(j).w);
queue.add(new Pair(to,d[to]));
}
}
class Pair implements Comparable<Pair>
{
int x;
int curMin;
Pair(int x,int CurMin)
{
this.x=x;
this.curMin=curMin;
}
@Override
public int compareTo(Pair k)
{
if(curMin>k.curMin)return 1;
if(curMin<k.curMin)return -1;
// TODO Auto-generated method stub
return 0;
}
}