zoukankan      html  css  js  c++  java
  • SPFA算法学习笔记

    一.理论准备

            为了学习网络流,先水一道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: 
  • 相关阅读:
    codevs1288 埃及分数
    codevs1792 分解质因数
    dp
    JAVA大数贪心
    求最长不重叠子串
    初识后缀数组
    dp
    两数相除,判断小数位是否有限位
    构造二分图匹配
    建立多个树状数组
  • 原文地址:https://www.cnblogs.com/hxsyl/p/3248391.html
Copyright © 2011-2022 走看看