算法複習(二)—— 最短路徑

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

}
点赞