SPFA-hdu4076

SPFA

SPFA(Shortest Path Faster Algorithm)算法,是西南交通大學段凡丁於 1994 年發表的,其在Bellman-ford算法的基礎上加上一個隊列優化,減少了冗餘的鬆弛操作,是一種高效的最短路算法。

SPFA和Bellman-ford一般都是用來處理帶負權的最短路問題(Dijkstra不能處理帶負權迴路的最短路),而SPFA是在Bellman-ford的基礎上優化的,所以我先介紹一下Bellman-ford算法的思想。

Bellman-ford思想
設頂點數爲 n,邊數爲 m,源點爲 source,數組dist[i]記錄從源點 source 到頂點 i 的最短路徑,除了dist[source]初始化爲 0 外,其它dist[]皆初始化爲 MAX。
然後循環 n-1 次,每次循環遍歷所有的邊e(u,v),然後進行鬆弛操作(就是dist[u] + w(u, v) < dist[v],則使 dist[v] = dist[u] + w(u, v),其中 w(u, v) 爲邊 e(u, v) 的權值),n-1次循環後從源點到其它所有點的最短路徑就確定了。
那麼怎麼判斷負權?想一想,本來經過n-1次循環,所有點的最短路徑應該已經確定。但是如果存在負權迴路,那麼可以不斷在負權迴路那裏循環,不斷減小(其實就是最短路可以無限小)。所以我們進行第n次循環,如果還能進行鬆弛操作,說明帶負權迴路,不能說明不帶。

SPFA思想
SPFA就是用一個隊列來優化Bellman-ford。
設立一個隊列用來保存待優化的節點,優化時每次取出隊首節點 u,並且用 u 點當前的最短路徑估計值dist[u]對與 u 點鄰接的頂點 v 進行鬆弛操作,如果 v 點的最短路徑估計值dist[v]可以更小,且 v 點不在當前的隊列中,就將 v 點放入隊尾。這樣不斷從隊列中取出頂點來進行鬆弛操作,直至隊列空爲止。
SPFA怎麼判斷負權迴路?如果某個點進入隊列的次數大於等於 n,則存在負權迴路,其中 n 爲圖的頂點數。因爲即使一個點與其它所有點都存在路徑,每次都能放入隊列,最多也就是n-1次,多的話肯定是存在負權迴路使它能不斷進入負權迴路。

對於最短路問題,個人覺得如果不帶負權的話最好用鄰接表+最小堆優化(優先隊列優化)的Dijkstra(邊數少也可考慮用SPFA),複雜度適宜,O((n+m)logn ),也穩定。如果帶負權迴路就用SPFA,時間複雜度(最好O(m),最差O(n*m)),不穩定。

hdu4076

具體看代碼註釋

#include <bits/stdc++.h>
using namespace std;

#define INF 0x3f3f3f3f

int w,h;
int dis[4][2]={-1,0,0,-1,1,0,0,1};//上下左右走
struct node{
    int x,y;
};
int matrix[35][35],dist[35][35],vis[35][35],nx[35][35],ny[35][35];
//matrix保存地圖狀態,0是草,-1是墓碑,1是洞
//dist[i][j]保存到i,j的最短時間,vis保存訪問狀態
//nx,ny保存洞的終點座標
int t[35][35],inqueue_num[35][35];
//t保存洞的用時,inqueue_num保存入隊次數

bool SPFA()
{
    bool flag=true;
    node p,next;
    queue<node>q;
    //起點入隊
    p.x=p.y=0;
    dist[0][0]=0;
    vis[0][0]=1;
    inqueue_num[0][0]++;
    q.push(p);
    while(!q.empty())
    {
        p = q.front(),q.pop();
        vis[p.x][p.y]=0;
        if(p.x==w-1&&p.y==h-1) continue;  //到達終點之後就不用再入隊鬆弛了
        if(matrix[p.x][p.y])  //有洞
        {
            next.x = nx[p.x][p.y];
            next.y = ny[p.x][p.y];
            if(dist[next.x][next.y]>dist[p.x][p.y]+t[p.x][p.y])  //如果能更新
            {
                dist[next.x][next.y]=dist[p.x][p.y]+t[p.x][p.y];  //鬆弛
                if(!vis[next.x][next.y])
                {
                    q.push(next);
                    inqueue_num[next.x][next.y]++;
                    if(inqueue_num[next.x][next.y]>=w*h) return flag=false;  //存在負權迴路
                    vis[next.x][next.y]=1;
                }
            }
            continue;  //有洞必須到洞的終點
        }
        for(int i=0;i<4;i++)  //上下左右遍歷
        {
            next.x = p.x+dis[i][0];
            next.y = p.y+dis[i][1];
            //超出地圖範圍或有墓碑
            if(next.x<0 || next.x>w-1 || next.y<0 || next.y>h-1 || matrix[next.x][next.y]==-1) continue;
            if(dist[next.x][next.y]>dist[p.x][p.y]+1)
            {
                dist[next.x][next.y]=dist[p.x][p.y]+1;
                if(!vis[next.x][next.y])
                {
                    q.push(next);
                    inqueue_num[next.x][next.y]++;
                    if(inqueue_num[next.x][next.y]>=w*h) return flag=false;
                    vis[next.x][next.y]=1;
                }
            }
        }
    }
    return flag;
}

int main()
{
    while(~scanf("%d%d",&w,&h)&&w!=0&&h!=0)
    {
        int num,x1,x2,y1,y2,tmp;
        memset(matrix,0,sizeof(matrix));
        memset(dist,INF,sizeof(dist));
        memset(vis,0,sizeof(vis));
        memset(inqueue_num,0,sizeof(inqueue_num));
        scanf("%d",&num);
        while(num--)
        {
            scanf("%d%d",&x1,&y1);
            matrix[x1][y1] = -1;
        }
        scanf("%d",&num);
        while(num--)
        {
            scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&tmp);
            matrix[x1][y1]=1;
            nx[x1][y1]=x2,ny[x1][y1]=y2;
            t[x1][y1]=tmp;
        }
        if(SPFA())
        {
            if(dist[w-1][h-1]==INF) printf("Impossible\n"); //不能到達終點
            else printf("%d\n",dist[w-1][h-1]);
        }
        else printf("Never\n");  //有負權迴路
    }
    return 0;
}
發佈了82 篇原創文章 · 獲贊 37 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章