最短路径
Dijkstra和单源最短路径
带权图的最短路径
Dijkstra算法
不能处理负权边,不过大量应用不依赖负权边
import java.util.Arrays;
public class Dijkstra {
private WeightedGraph G;
private int s;
private int[] dis;
private boolean[] visited;
public Dijkstra(WeightedGraph G, int s){
this.G = G;
G.validateVertex(s);
this.s = s;
dis = new int[G.V()];//开空间
Arrays.fill(dis, Integer.MAX_VALUE);//为dis中所有元素幅值无穷大(int中最大值
dis[s] = 0;//s离源的位置是0
visited = new boolean[G.V()];//给visitied开空间,这里默认是false
while(true){
//1.找当前没有遍历的最短路节点,存到cur里
int cur = -1, curdis = Integer.MAX_VALUE;
for(int v = 0; v < G.V(); v ++)
if(!visited[v] && dis[v] < curdis){//v的dis值小于当前找到的最小dis值
curdis = dis[v];
cur = v;
}//第一个循环必然更新cur为s,此时的循环逻辑还未正式开始
if(cur == -1) break;
//2.确定当前最小dis值,就算源点到它的最短路径
visited[cur] = true;
//3.根据这个节点的最短路径大小,更新其他节点路径的大小
for(int w: G.adj(cur))//遍历这个cur顶点的所有相邻顶点
if(!visited[w]){//w的最短路径如果还未得到的话,通过这层循环,对还未得到最短路径的节点进行更新
if(dis[cur] + G.getWeight(cur, w) < dis[w])//s-cur加cur-w<s-w,意味着找到了更短路径
dis[w] = dis[cur] + G.getWeight(cur, w);//更新最小路径dis[w]
}
}
}
public boolean isConnectedTo(int v){
G.validateVertex(v);
return visited[v];
}
public int distTo(int v){
G.validateVertex(v);
return dis[v];
}
static public void main(String[] args){
WeightedGraph g = new WeightedGraph("g.txt");
Dijkstra dij = new Dijkstra(g, 0);
for(int v = 0; v < g.V(); v ++)
System.out.print(dij.distTo(v) + " ");
System.out.println();
}
}
输入
5 8
0 1 4
0 2 2
1 2 1
1 3 2
1 4 3
2 3 4
2 4 5
3 4 1
输出
0 3 2 5 6
时间复杂度:O(V^2)
优化
import java.util.Arrays;
import java.util.PriorityQueue;
public class Dijkstra {
private WeightedGraph G;
private int s;
private int[] dis;
private boolean[] visited;
private class Node implements Comparable<Node>{
public int v, dis;//顶点与dis
public Node(int v, int dis){
this.v = v;
this.dis = dis;
}
@Override
public int compareTo(Node another){
return dis - another.dis;
}
}
public Dijkstra(WeightedGraph G, int s){
this.G = G;
G.validateVertex(s);
this.s = s;
dis = new int[G.V()];
Arrays.fill(dis, Integer.MAX_VALUE);
dis[s] = 0;
visited = new boolean[G.V()];
PriorityQueue<Node> pq = new PriorityQueue<Node>();//存顶点序号与dis值
pq.add(new Node(s, 0));//添加源点与其dis值
while(!pq.isEmpty()){
//1.找当前没有遍历的最短路节点,存到cur里
int cur = pq.remove().v;
if(visited[cur]) continue;//如果cur已经处理过了,就接着去弄下一个
//2.确定当前最小dis值,就算源点到它的最短路径
visited[cur] = true;
//3.根据这个节点的最短路径大小,更新其他节点路径的大小
for(int w: G.adj(cur))
if(!visited[w]){
if(dis[cur] + G.getWeight(cur, w) < dis[w]){
dis[w] = dis[cur] + G.getWeight(cur, w);//优先队列不能动态更新
pq.add(new Node(w, dis[w]));//所以每更新一个w,往Node中添加一个节点,方便下一次比较
}
}
}
}
public boolean isConnectedTo(int v){
G.validateVertex(v);
return visited[v];
}
public int distTo(int v){
G.validateVertex(v);
return dis[v];
}
static public void main(String[] args){
WeightedGraph g = new WeightedGraph("g.txt");
Dijkstra dij = new Dijkstra(g, 0);
for(int v = 0; v < g.V(); v ++)
System.out.print(dij.distTo(v) + " ");
System.out.println();
}
}
时间复杂度O(ElogE)
求解最短路径:pre数组
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.PriorityQueue;
public class Dijkstra {
private WeightedGraph G;
private int s;
private int[] dis;
private boolean[] visited;
private int[] pre;
private class Node implements Comparable<Node>{
public int v, dis;
public Node(int v, int dis){
this.v = v;
this.dis = dis;
}
@Override
public int compareTo(Node another){
return dis - another.dis;
}
}
public Dijkstra(WeightedGraph G, int s){
this.G = G;
G.validateVertex(s);
this.s = s;
dis = new int[G.V()];
Arrays.fill(dis, Integer.MAX_VALUE);
pre = new int[G.V()];
Arrays.fill(pre, -1);//pre默认值是-1
dis[s] = 0;
pre[s] = s;//源点上一个顶点是它自己
visited = new boolean[G.V()];
PriorityQueue<Node> pq = new PriorityQueue<Node>();
pq.add(new Node(s, 0));
while(!pq.isEmpty()){
int cur = pq.remove().v;
if(visited[cur]) continue;
visited[cur] = true;
for(int w: G.adj(cur))
if(!visited[w]){
if(dis[cur] + G.getWeight(cur, w) < dis[w]){
dis[w] = dis[cur] + G.getWeight(cur, w);
pq.add(new Node(w, dis[w]));
pre[w] = cur;//确认最短路径后,确认前一个点
}
}
}
}
public boolean isConnectedTo(int v){
G.validateVertex(v);
return visited[v];
}
public int distTo(int v){
G.validateVertex(v);
return dis[v];
}
public Iterable<Integer> path(int t){//可迭代的一个对象
ArrayList<Integer> res = new ArrayList<>();//开空间
if(!isConnectedTo(t)) return res;//是否可达t
int cur = t;//从t开始不断往回找,获得路径
while(cur != s){
res.add(cur);
cur = pre[cur];
}
res.add(s);
Collections.reverse(res);
return res;
}
static public void main(String[] args){
WeightedGraph g = new WeightedGraph("g.txt");
Dijkstra dij = new Dijkstra(g, 0);
for(int v = 0; v < g.V(); v ++)
System.out.print(dij.distTo(v) + " ");
System.out.println();
System.out.println(dij.path(3));
}
}
Bellman-Ford算法
有方向性
松弛操作
不同松弛顺序,值有不同
但最多V-1轮,无负权环时一定得到固定解
import java.util.Arrays;
public class BellmanFord {
private WeightedGraph G;
private int s;
private int[] dis;
private boolean hasNegCycle = false;
public BellmanFord(WeightedGraph G, int s){
this.G = G;
G.validateVertex(s);
this.s = s;
dis = new int[G.V()];
Arrays.fill(dis, Integer.MAX_VALUE);//为dis数组赋初始值
dis[s] = 0;
//V-1轮的松弛操作
for(int pass = 1; pass < G.V(); pass ++){
for(int v = 0; v < G.V(); v ++)//遍历所有顶点
for(int w: G.adj(v))//遍历所有顶点的相邻顶点
if(dis[v] != Integer.MAX_VALUE && dis[v] + G.getWeight(v, w) < dis[w])//松弛操作
dis[w] = dis[v] + G.getWeight(v, w);
}
//再做一次松弛操作,判断是否含负权环
for(int v = 0; v < G.V(); v ++)
for(int w : G.adj(v))
if(dis[v] != Integer.MAX_VALUE && dis[v] + G.getWeight(v, w) < dis[w])
hasNegCycle = true;
}
public boolean hasNegativeCycle(){
return hasNegCycle;
}
public boolean isConnectedTo(int v){
G.validateVertex(v);
return dis[v] != Integer.MAX_VALUE;
}
//返回去v的最短路径
public int distTo(int v){
G.validateVertex(v);
if(hasNegCycle) throw new RuntimeException("exist negative cycle.");
return dis[v];
}
static public void main(String[] args){
WeightedGraph g = new WeightedGraph("g.txt");
BellmanFord bf = new BellmanFord(g, 0);
if(!bf.hasNegativeCycle()){
for(int v = 0; v < g.V(); v ++)
System.out.print(bf.distTo(v) + " ");
System.out.println();
}
else
System.out.println("exist negative cycle.");
WeightedGraph g2 = new WeightedGraph("g2.txt");
BellmanFord bf2 = new BellmanFord(g2, 0);
if(!bf2.hasNegativeCycle()){
for(int v = 0; v < g2.V(); v ++)
System.out.print(bf2.distTo(v) + " ");
System.out.println();
}
else
System.out.println("exist negative cycle.");
}
}
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
public class BellmanFord {
private WeightedGraph G;
private int s;
private int[] dis;
private int[] pre;
private boolean hasNegCycle = false;
public BellmanFord(WeightedGraph G, int s){
this.G = G;
G.validateVertex(s);
this.s = s;
dis = new int[G.V()];
Arrays.fill(dis, Integer.MAX_VALUE);
dis[s] = 0;
pre = new int[G.V()];
Arrays.fill(pre, -1);
for(int pass = 1; pass < G.V(); pass ++){
for(int v = 0; v < G.V(); v ++)
for(int w: G.adj(v))
if(dis[v] != Integer.MAX_VALUE &&
dis[v] + G.getWeight(v, w) < dis[w]){ dis[w] = dis[v] + G.getWeight(v, w);
pre[w] = v;//跟新完幅值
}
}
for(int v = 0; v < G.V(); v ++)
for(int w : G.adj(v))
if(dis[v] != Integer.MAX_VALUE &&
dis[v] + G.getWeight(v, w) < dis[w])
hasNegCycle = true;
}
public boolean hasNegativeCycle(){
return hasNegCycle;
}
public boolean isConnectedTo(int v){
G.validateVertex(v);
return dis[v] != Integer.MAX_VALUE;
}
public int distTo(int v){
G.validateVertex(v);
if(hasNegCycle) throw new RuntimeException("exist negative cycle.");
return dis[v];
}
public Iterable<Integer> path(int t){//返回最短路径
ArrayList<Integer> res = new ArrayList<Integer>();
if(!isConnectedTo(t)) return res;
int cur = t;
while(cur != s){
res.add(cur);
cur = pre[cur];
}
res.add(s);
Collections.reverse(res);
return res;
}
static public void main(String[] args){
WeightedGraph g = new WeightedGraph("g.txt");
BellmanFord bf = new BellmanFord(g, 0);
if(!bf.hasNegativeCycle()){
for(int v = 0; v < g.V(); v ++)
System.out.print(bf.distTo(v) + " ");
System.out.println();
System.out.println(bf.path(3));
}
else
System.out.println("exist negative cycle.");
WeightedGraph g2 = new WeightedGraph("g2.txt");
BellmanFord bf2 = new BellmanFord(g2, 0);
if(!bf2.hasNegativeCycle()){
for(int v = 0; v < g2.V(); v ++)
System.out.print(bf2.distTo(v) + " ");
System.out.println();
}
else
System.out.println("exist negative cycle.");
}
}
Floyed算法
求解所有点对最短路径--求解出图的直径--所有点对最短路径中的最大值
都是一个意思
v-b的最短路径+b-a的最短路径小于v-a的最短路径,跟新dis[v][a]
import java.util.Arrays;
public class Floyed {
private WeightedGraph G;
private int[][] dis;
private boolean hasNegCycle = false;
public Floyed(WeightedGraph G){
this.G = G;
//初始化过程
dis = new int[G.V()][G.V()];
for(int v = 0; v < G.V(); v ++)
Arrays.fill(dis[v], Integer.MAX_VALUE);
for(int v = 0; v < G.V(); v ++){//不是所有的距离都幅正无穷,如果v-w相连,赋dis为 边的权值
dis[v][v] = 0;
for(int w: G.adj(v))
dis[v][w] = G.getWeight(v, w);
}
for(int t = 0; t < G.V(); t ++)//遍历中间寻找任意两点间的最短路径多经过的点
for(int v = 0; v < G.V(); v ++)//遍历两点间最短路径的两个端点是谁
for(int w = 0; w < G.V(); w ++)
if(dis[v][t] != Integer.MAX_VALUE && dis[t][w] != Integer.MAX_VALUE
&& dis[v][t] + dis[t][w] < dis[v][w])//防止整型溢出
dis[v][w] = dis[v][t] + dis[t][w];
//判断是否有负权环
for(int v = 0; v < G.V(); v ++)
if(dis[v][v] < 0)
hasNegCycle = true;
}
public boolean hasNegativeCycle(){
return hasNegCycle;
}
public boolean isConnectedTo(int v, int w){
G.validateVertex(v);
G.validateVertex(w);
return dis[v][w] != Integer.MAX_VALUE;
}
public int distTo(int v, int w){//返回两点最短路径
G.validateVertex(v);
G.validateVertex(w);
return dis[v][w];
}
static public void main(String[] args){
WeightedGraph g = new WeightedGraph("g.txt");
Floyed floyed = new Floyed(g);
if(!floyed.hasNegativeCycle()){
for(int v = 0; v < g.V(); v ++){
for(int w = 0; w < g.V(); w ++)
System.out.print(floyed.distTo(v, w) + " ");
System.out.println();
}
}
else
System.out.println("exist negative cycle.");
WeightedGraph g2 = new WeightedGraph("g2.txt");
Floyed floyed2 = new Floyed(g2);
if(!floyed2.hasNegativeCycle()){
for(int v = 0; v < g.V(); v ++){
for(int w = 0; w < g.V(); w ++)
System.out.print(floyed2.distTo(v, w) + " ");
System.out.println();
}
}
else
System.out.println("exist negative cycle.");
}
}