Dijkstra算法
Dijkstra算法使用广度优先搜索解决赋权有向图或者无向图的单源最短路径问题,该种算法最终可以得到一个最短路径树。
Dijkstra算法采用一种贪心策略,声明一个数组dis保存源点到各个节点的最短距离和一个已经找到最短路径的节点集合。初始化时,源点u到节点v的路径权重被赋值为0(dis[s]=0)。对于节点v存在能直接到达(v,u),则把dis[u]设为w(v,u),同时把不能到达的距离设为无限大。
具体步骤:
(1)从数组dis选择最小值,该值就是原点u到其对应节点的最短路径,因此此点已经是源点能够到达的最短路径,不可能通过其他节点再次中转得到更短距离,同时将一个vis数组里把它设置为true(初始化vis数组都为false)。
(2)需要看看新加入的节点是否可到达其他节点,并看看通过该节点到达其他节点的路径长度是否比从源点直接到达更短,如果是,替换dis数组的值。
(3)从dis数组中找到最小值,更新数组vis。重复以上步骤,直到所有节点被遍历完。
数组实现
给出一幅有向权重图:
节点A对应0,节点B对应1,以此类推。
package Graph;
import java.util.*;
import java.io.*;
public class Dijkstra {
private static final int MAX_VALUE = Integer.MAX_VALUE;//设置最大值
private int[][] edges;//边
private int[] dist;//源点到各个节点的距离
private boolean[] visited;//是否遍历过
private int[] pre;//前一个节点
private int n;//节点个数
private int start;//源点
public Dijkstra(int[][] edges, int n, int start) {
this.edges = edges;
this.n = n;
this.start = start;
}
public void dijkstra() {
//初始化
dist = new int[n];
visited = new boolean[n];
pre = new int[n];
Arrays.fill(dist, MAX_VALUE);
Arrays.fill(visited, false);
dist[start] = 0;
for (int i = 0; i < n; i++) {
int u = minDistance();//获得最小距离节点
visited[u] = true;
for (int j = 0; j < n; j++) {
if (!visited[j] && edges[u][j] != 0 && dist[u] != MAX_VALUE && dist[u] + edges[u][j] < dist[j]) {
dist[j] = dist[u] + edges[u][j];//更新
pre[j] = u;
}
}
}
}
//获得最小距离
private int minDistance() {
int min = MAX_VALUE;
int minIndex = 0;
for (int i = 0; i < n; i++) {
if (visited[i] == false && dist[i] <= min) {
min = dist[i];
minIndex = i;
}
}
return minIndex;
}
//打印路径
public void printPath(int target) {
System.out.print("Path: " + target);
int i = target;
while (i != start) {
System.out.print(" <- " + pre[i]);
i = pre[i];
}
System.out.println("\n最短距离: " + dist[target]);
}
public static void main(String[] args) {
Scanner in=new Scanner(new BufferedInputStream(System.in));
while(in.hasNext()){
//初始化输入数据
int n=in.nextInt();
int [][]edges=new int[n][n];
System.out.println("输入 :'0 0 0'结束输出 ");
while (true){
int v=in.nextInt(),u= in.nextInt(),w= in.nextInt();
if(v==0&&u==0&&w==0)break;
edges[v][u]=w;
}
System.out.println("输入源点");
Dijkstra djk=new Dijkstra(edges,n,in.nextInt());
//执行
djk.dijkstra();
//打印路径
System.out.println("输入目的点,打印路径");
int target= in.nextInt();
djk.printPath(target);
}
}
}
输入边集:
0 1 10
1 2 50
2 4 10
3 2 20
3 4 60
0 4 100
0 3 30
0 0 0
执行操作演示,控制台效果:
优先队列实现
package Graph;
import java.util.*;
import java.io.*;
public class DijkstraPriorityQueue {
private static final int MAX_VALUE = Integer.MAX_VALUE;//无限大
private Map<Integer,Map<Integer,Integer>>edges;//边
private int[] dist;//源点到各个节点的距离
private boolean[] visited;//是否遍历过
private int[] pre;//前一个节点
private int n;//节点个数
private int start;//源点
public DijkstraPriorityQueue(Map<Integer, Map<Integer, Integer>> edges, int n, int start) {
this.edges = edges;
this.n = n;
this.start = start;
}
public void dijkstra() {
dist = new int[n];
visited = new boolean[n];
pre = new int[n];
Arrays.fill(dist, MAX_VALUE);
Arrays.fill(visited, false);
dist[start] = 0;
//定义优先队列
PriorityQueue<Integer> queue = new PriorityQueue<>(n, new Comparator<Integer>() {
@Override
public int compare(Integer u, Integer v) {
return dist[u] - dist[v];
}
});
queue.offer(start);
while (!queue.isEmpty()) {
int u = queue.poll();
visited[u] = true;
for (Map.Entry<Integer, Integer> entry : edges.get(u).entrySet()) {
int v = entry.getKey();
int weight = entry.getValue();
if (!visited[v] && dist[u] != MAX_VALUE && dist[u] + weight < dist[v]) {
dist[v] = dist[u] + weight;
pre[v] = u;
queue.offer(v);
}
}
}
}
public void printPath(int target) {
System.out.print("Path: " + target);
int i = target;
while (i != start) {
System.out.print(" <- " + pre[i]);
i = pre[i];
}
System.out.println("\n最短距离: " + dist[target]);
}
public static void main(String[] args) {
Scanner in = new Scanner(new BufferedInputStream(System.in));
while (in.hasNext()) {
//初始化输入数据
int n = in.nextInt();
Map<Integer, Map<Integer, Integer>> edges = new HashMap<>();
for (int i = 0; i < n; i++) {
edges.put(i, new HashMap<>());
}
System.out.println("输入 :'0 0 0'结束输出 ");
while (true) {
int v = in.nextInt(), u = in.nextInt(), w = in.nextInt();
if (v == 0 && u == 0 && w == 0) break;
Map<Integer, Integer> tmp = new HashMap<>();
edges.get(v).put(u, w);
}
System.out.println("输入源点");
DijkstraPriorityQueue djkprq = new DijkstraPriorityQueue(edges, n, in.nextInt());
//执行
djkprq.dijkstra();
//打印路径
System.out.println("输入目的点,打印路径");
int target = in.nextInt();
djkprq.printPath(target);
}
}
}
控制台:
Folyd算法
Floyd算法是一种用于求多源最短路径的算法。它适用于边权可能为负的情况.
Floyd算法的基本思想是,对于每对结点i和j,通过中间结点k来更新从i到j的最短路径。算法枚举每一个结点作为中间结点,并用这个中间结点来更新所有结点对之间的最短路径.
时间复杂度 O(n^3),Floyd算法在大多数情况下都比Dijkstra算法慢,因为它遍历了所有可能的结点对. 它在稠密图上使用,或者在图中存在负权边时非常有用.
图:
伪代码:
1 let dist be a |V| × |V| array of minimum distances initialized to ∞ (infinity)
2 for each vertex v
3 dist[v][v] ← 0
4 for each edge (u,v)
5 dist[u][v] ← w(u,v) // the weight of the edge (u,v)
6 for k from 1 to |V|
7 for i from 1 to |V|
8 for j from 1 to |V|
9 if dist[i][j] > dist[i][k] + dist[k][j]
10 dist[i][j] ← dist[i][k] + dist[k][j]
11 end if
package Graph;
import java.util.*;
import java.io.*;
public class Floyd {
private static final int INF = Integer.MAX_VALUE;
private int[][] dist;
private int[][] path;
public void floyd(int[][] graph) {
int n = graph.length;
dist = new int[n][n];
path = new int[n][n];
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
dist[i][j] = graph[i][j];
path[i][j] = -1;
}
}
for (int k = 0; k < n; k++) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (dist[i][k] != INF && dist[k][j] != INF && dist[i][j] > dist[i][k] + dist[k][j]) {
dist[i][j] = dist[i][k] + dist[k][j];
path[i][j] = k;
}
}
}
}
}
public static void main(String[] args) {
Scanner in = new Scanner(new BufferedInputStream(System.in));
while (in.hasNext()) {
//初始化输入数据
int n = in.nextInt();
int graph[][] = new int[n][n];
for (int i = 0; i < n; i++) {
Arrays.fill(graph[i], INF);
}
System.out.println("输入 :'0 0 0'结束输出 ");
while (true) {
int v = in.nextInt(), u = in.nextInt(), w = in.nextInt();
if (v == 0 && u == 0 && w == 0) break;
graph[v][u] = w;
}
Floyd f = new Floyd();
f.floyd(graph);
System.out.println("输入两个节点");
int v = in.nextInt(), u = in.nextInt();
System.out.println(v + " -> " + u + "最短距离为:" + f.dist[v][u] + " 经过节点 " + f.path[v][u]);
}
}
}
控制台
步骤如下:
- 初始化:将图中每个顶点到其他顶点的距离设置为边权。如果两点之间没有边相连,则距离为无穷大。
- 三重循环:从1至n(n为顶点数)依次遍历每个顶点,设该顶点为中间点。
- 更新距离:通过遍历中间点,更新从i到j顶点之间的最短距离(d[i][j])。如果通过中间点k使得i到j的距离更小,则更新d[i][j] = d[i][k]+d[k][j]
- 循环结束后,d[i][j] 存储的就是i到j的最短路径了。