经典的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();
}
}