經典的Dijkstra最短路徑算法只適用沒有負邊的情況。因爲該算法,一個點到起點的最短距離一旦確定(採用貪心策略),它就不會再更改。而負邊會破壞這個貪心的正確性。
對於有負邊的情況,可以使用bellman-ford算法。它的思想是一條路徑最多由n-1條邊構成。因此,它從起始點出發,對圖進行n-1次鬆弛操作。鬆弛操作就是藉助一條邊使得一個點到起點的距離變小。注意,不同於Dijkstra算法,一個點到起點的最短距離並不是一次確定的。原始的bellman-ford算法每一次鬆弛都遍歷所有的邊。這樣是不必要的,因爲只有一個被鬆弛過的點纔可能有助於其它點的鬆弛。因此,有bellman-ford算法的各種優化版本,如大家熟知的SPFA算法。使用鄰接邊存儲結構,SPFA的算法複雜度爲O(k*e),據稱通常k不大於2。因此,就算是求不含負邊的最短路徑問題,我們也可以使用SPFA。
在最短路徑問題中,另一個經典算法就是求出所有點對之間的算法——Floyd算法。可以看到,Dijkstra, bellman-ford或SPFA根據一條邊上的一個點鬆弛另一個點。不同地,Floyd採用動態規劃的思想,狀態轉移方程可以參見該博文。其複雜度爲O(n^3)
我分別寫了兩個graph類,MatrixGraph使用鄰接矩陣存儲結構,LinkedGraph使用鄰接表結構。前者能很自然地實現Floyd算法,後者的實現只是調用了n次SPFA(理論上不會慢,但多次SPFA涉及到更多object,包括創建,垃圾回收等,所以實際很慢)
用於驗證正確性的是hdu 1874
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
//1874 hdu
interface Graph {
public void addEdge(int u, int v, float weight);
public void spfa(int start);
public float disBtw(int start, int dest);
public void floyd();
}
// 鄰接矩陣版
// 時間複雜度O(k*v*v), k一般不大於2
class MatrixGraph implements Graph{
final static float MAXW = Float.MAX_VALUE;
int eCnt;
int nCnt;
float con[][];
boolean inQueue[];
boolean bidir;
int iqCnt[];
float disMatrix[][];
MatrixGraph() {
}
MatrixGraph(int nCnt, boolean bidir) {
this.nCnt = nCnt;
this.bidir = bidir;
nCnt++;
con = new float[nCnt][nCnt];
inQueue = new boolean[nCnt];
iqCnt = new int[nCnt];
disMatrix = new float[nCnt][];
for(int i=0; i<nCnt; i++) {
for(int j=0; j<nCnt; j++) {
if(i != j ) {
con[i][j] = MAXW;
} else {
con[i][j] = 0;
}
}
}
for(int i=0; i<nCnt; i++) {
inQueue[i] = false;
iqCnt[i] = 0;
disMatrix[i]= null;
}
}
public void addEdge(int u, int v, float weight) {
if(weight < con[u][v]) {
con[u][v] = weight;
if(!bidir) {
con[v][u] = weight;
}
}
}
public void spfa(int start) {
float dis[] = new float[nCnt+1];
for(int i = 0; i < nCnt; i++) {
dis[i] = MAXW;
}
LinkedList<Integer> queue = new LinkedList<Integer>();
dis[start] = 0;
queue.push(start);
iqCnt[start] = 1;
while(!queue.isEmpty()) {
int cur = queue.pop();
inQueue[cur] = false;
for(int i=0; i<nCnt; i++) {
if(i!=cur && con[cur][i]!= MAXW) {
if(dis[i] > dis[cur]+con[cur][i]) {
dis[i] = dis[cur]+con[cur][i];
if(inQueue[i] == false) {
queue.push(i);
iqCnt[i]++;
inQueue[i] = true;
}
}
}
}
}
disMatrix[start] = dis;
}
public void floyd() {
for (int i = 0; i < disMatrix.length; i++) {
disMatrix[i] = new float[nCnt+1];
System.arraycopy(con[i], 0, disMatrix[i], 0, con[i].length);
}
for(int k = 0; k < nCnt; k++)
{
for(int i = 0; i < nCnt; i++)
{
for(int j = 0; j < nCnt; j++)
{
if(disMatrix[i][k] < MAXW && disMatrix[k][j] < MAXW) {
if( disMatrix[i][j] > disMatrix[i][k] + disMatrix[k][j]) {
disMatrix[i][j] = disMatrix[i][k] + disMatrix[k][j];
}
}
}
}
}
}
public float disBtw(int start, int dest) {
if(disMatrix[start]!=null && disMatrix[start][dest] != MAXW) {
return disMatrix[start][dest];
} else {
return -1;
}
}
}
//鄰接邊版 (一般比鄰接矩陣快,因爲圖通常不太稠密)
//時間複雜度O(k*e), k一般不大於2
class LinkedGraph implements Graph{
final static float MAXW = Float.MAX_VALUE;
int eCnt;
int nCnt;
HashMap<Integer, Float> head[];
boolean inQueue[];
float disMatrix[][];
boolean bidir;
int iqCnt[];
LinkedGraph() {
}
LinkedGraph(int nCnt, boolean bidir) {
this.nCnt = nCnt;
this.bidir = bidir;
nCnt++;
head = new HashMap[nCnt];
inQueue = new boolean[nCnt];
disMatrix = new float[nCnt][];
iqCnt = new int[nCnt];
for(int i=0; i<nCnt; i++) {
head[i] = new HashMap<Integer, Float>();
}
for(int i=0; i<nCnt; i++) {
inQueue[i] = false;
iqCnt[i] = 0;
disMatrix[i] = null;
}
}
public void addEdge(int u, int v, float weight) {
Float curW = head[u].get(v);
if(curW == null) {
head[u].put(v, weight);
if(!bidir) {
head[v].put(u, weight);
}
}
else if(curW != null && curW>weight) {
head[u].put(v, weight);
if(!bidir) {
head[v].put(u, weight);
}
}
}
public void spfa(int start) {
LinkedList<Integer> queue = new LinkedList<Integer>();
float[] dis = new float[nCnt+1];
for(int i = 0; i < nCnt; i++) {
dis[i] = MAXW;
}
dis[start] = 0;
queue.push(start);
// iqCnt[start] = 1;
while(!queue.isEmpty()) {
int cur = queue.pop();
inQueue[cur] = false;
for(Map.Entry<Integer,Float> entry : head[cur].entrySet()) {
int nextNode = entry.getKey();
float weight = entry.getValue();
if(dis[nextNode] > dis[cur]+weight) {
dis[nextNode] = dis[cur]+weight;
if(inQueue[nextNode] == false) {
queue.push(nextNode);
// iqCnt[nextNode]++;
inQueue[nextNode] = true;
}
}
}
}
disMatrix[start] = dis;
}
// it may be very slow
// as it involves one queue for every node in every graph
// this is a fake-floyd implementation
public void floyd() {
for(int i = 0; i < nCnt; i++) {
spfa(i);
}
}
public float disBtw(int start, int dest) {
if(disMatrix[start]!=null && disMatrix[start][dest] != MAXW) {
return disMatrix[start][dest];
} else {
return -1;
}
}
}
public class Main {
public static void main(String[] args) throws IOException{
int eCnt, nCnt;
int u, v;
float weight;
int depa, dest;
StreamTokenizer in = new StreamTokenizer(
new BufferedReader(new InputStreamReader(System.in)));
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
while(in.nextToken() != StreamTokenizer.TT_EOF) {
nCnt = (int)in.nval;
in.nextToken();
eCnt = (int)in.nval;
// MatrixGraph graph = new MatrixGraph(nCnt, false);
LinkedGraph graph = new LinkedGraph(nCnt, false);
for(int i=0; i<eCnt; i++) {
in.nextToken();
u = (int)in.nval;
in.nextToken();
v = (int)in.nval;
in.nextToken();
weight = (float)in.nval;
graph.addEdge(u, v, weight);
}
in.nextToken();
depa = (int)in.nval;
in.nextToken();
dest = (int)in.nval;
// graph.spfa(depa);
graph.floyd();
out.println((int)graph.disBtw(depa, dest));
}
out.flush();
}
}