一.理論准備
為了學習網絡流,先水一道spfa。
SPFA算法是1994年西南交通大學段凡丁提出,只要最短路徑存在,SPFA算法必定能求出最小值,SPFA對Bellman-Ford算法優化的關鍵之處在於意識到:只有那些在前一遍松弛中改變了距離估計值的點,才可能引起他們的鄰接點的距離估計值的改變。為什么隊列為空就不改變了呢?就是因為要到下一點必須經過它的前一個鄰接點。。SPFA可以處理負權邊。很多時候,給定的圖存在負權邊,這時類似Dijkstra等算法便沒有了用武之地,而Bellman-Ford算法的復雜度又過高,SPFA算法便派上用場了。簡潔起見,我們約定有向加權圖G不存在負權回路,即最短路徑一定存在。當然,我們可以在執行該算法前做一次拓撲排序,以判斷是否存在負權回路。
初始化: dis數組全部賦值為Inf(無窮大,不能是map[s][i]),path數組全部賦值為s(即源點),或者賦值為-1,表示還沒有知道前驅,然后dis[s]=0; 表示源點不用求最短路徑,或者說最短路就是0。將源點入隊;另外記住在整個算法中有頂點入隊了要記得標記vis數組,有頂點出隊了記得消除那個標記(可能多次入隊)。
核心:讀取隊頭頂點u,並將隊頭頂點u出隊(記得消除標記);將與點u相連的所有點v進行松弛操作,如果能更新估計值(即令d[v]變小),那么就更新,另外,如果點v沒有在隊列中,那么要將點v入隊(記得標記),如果已經在隊列中了,那么就不用入隊以此循環,直到隊空為止就完成了單源最短路的求解。
判斷有無負環:如果某個點進入隊列的次數超過N次則存在負環(SPFA無法處理帶負環的圖),假設這個節點的入度是k(無向權則就是這個節點的連接的邊)如果進入這個隊列超過k,說明必然有某個邊重復了,即成環;換一種思路:用DFS,假設存在負環a1->a2->…->an->a1。那么當從a1深搜下去時又遇到了a1,那么直接可以判斷負環了所有用。當某個節點n次進入隊列,則存在負環,此時時間復雜度為O(n*m),n為節點,m為邊。
SPFA算法有兩個優化算法 SLF 和 LLL: SLF:Small Label First 策略,設要加入的節點是j,隊首元素為i,若dist(j)<dist(i),則將j插入隊首,否則插入隊尾。 LLL:Large Label Last 策略,設隊首元素為i,隊列中所有dist值的平均值為x,若dist(i)>x則將i插入到隊尾,查找下一元素,直到找到某一i使得dist(i)<=x,則將i出對進行松弛操作。 SLF 可使速度提高 15 ~ 20%;SLF + LLL 可提高約 50%。 在實際的應用中SPFA的算法時間效率不是很穩定,為了避免最壞情況的出現,通常使用效率更加穩定的Dijkstra算法。個人覺得LLL優化每次要求平均值,不太好,為了簡單,我們可以之間用c++STL里面的優先隊列來進行SLF優化。
二.算法實現直接去把HDU1874AC了吧。
1: import java.util.Comparator;
2: import java.util.PriorityQueue;
3: import java.util.Queue;
4: import java.util.Scanner;
5: /*
6: * 原來一直wa,重寫了一遍,AC了7: * SLF優化8: */9: public class HD1874 {10:11: static int n,m;12: static int[][] map = new int[205][205];13: static int[] dis = new int[205];14: static boolean[] vis = new boolean[205];15: /*
16: * 路徑最大值是10000,不能設置成10005就行,還要考慮和17: * 也不能是整形最大值,否則一加就溢出了18: */19: static final int Inf = 0x3f3f3f3f;20:21: public static void main(String[] args) {22: Scanner sc = new Scanner(System.in);
23: // 記錄前驅點 。若path[i]=j,表示從s到i的最短路徑中i的前一個點是j
24: //int[] path;
25: int u,v,w;
26: while(sc.hasNext()) {
27: n = sc.nextInt();28: m = sc.nextInt();29: for(int i=0; i<205; i++) {30: for(int j=i; j<205; j++) {31: map[i][j] = Inf;32: map[j][i] = Inf;33: }34: }35: for(int i=0; i<m; i++) {36: u = sc.nextInt();37: v = sc.nextInt();38: w = sc.nextInt();39: //多重邊
40: if(map[u][v]>w) {
41: map[v][u] = w;42: map[u][v] = w;43: }44: }45: int s = sc.nextInt();
46: int t = sc.nextInt();
47: spfa(s);48: //題目上有st<n,所以不必判斷dis[t]是否越界
49: //起點終點相同的話答案是0
50: if(Inf==dis[t]) {
51: System.out.println(-1);52: }else {
53: System.out.println(dis[t]);54: }55: }56: }57:58: private static void spfa(int s) {59:60: for(int i=0; i<205; i++) {61: vis[i] = false;
62: //初始化為map[s][i]第一組數據就錯了
63: dis[i] = Inf;64: }65: dis[s] = 0;66: vis[s] = true;
67: Comparator<Integer> cmp = new Comparator<Integer>() {
68:69: public int compare(Integer o1, Integer o2) {70: int i = (int)o1;71: int j = (int)02;72: if(dis[i]>dis[j]) {
73: return 1;
74: }else if(dis[i]==dis[j]){75: return 0;
76: }else {
77: return -1;
78: }79: }80: };81: //面向接口編程;205代表優先隊列(是類)的容量
82: Queue<Integer> q = new PriorityQueue<Integer>(205, cmp);
83: q.clear();84: q.offer(s);85: while(!q.isEmpty()) {
86: int head = q.poll();
87: //該注意的是有些點可能重復入隊,所以出隊的點也要重新置未標記
88: vis[head] = false;
89: for(int i=0; i<n; i++) {90: //dis[head]不可能是INF,map[head][i]可能是INF
91: int temp = dis[head] + map[head][i];
92: if(temp<dis[i]) {
93: //path[i] = head
94: dis[i] = temp;95: if(!vis[i]) {
96: //用一個數組在此記錄入隊次數,大於n就存在負環;如何事先判斷
97: q.offer(i);98: vis[i] = true;
99: }100: }101: }102: }103: }104:105: }106: