您的位置:首页 > 其它

最短路 - spfa - (二) 【 理解 + 例题 】 更新 ing......

2014-08-11 13:46 246 查看
在家的学习效率实在太低,想想暑假都要过完了,什么东西都没学到。

如果你还没理解spfa 的过程,可以先看一下 最短路 - spfa - (一)
这篇文章的图解,接下来,我们就好好讲一讲spfa算法。

SPFA(Shortest Path Faster Algorithm)算法是求单源最短路径的一种算法,在Bellman-ford算法的基础上加上一个队列优化,减少了冗余的松弛操作是一种高效的最短路算法。相比于Dijkstra,SPFA可以计算带负环的回路。邻接表的复杂度为:O(kE)E为边数,k一般为2或3,k为所有顶点进队的平均次数。

过程:用数组dis记录更新后的状态,cnt记录更新的次数,队列q记录更新过的顶点,算法依次从q中取出待更新的顶点U,按照dis(k)[u]的递归式计算。在计算过程中,一旦发现顶点K有cnt[ k ] > n,说明有一个从顶点K出发的负权圈,此时没有最短路,应终止算法。否则,队列为空的时候,算法得到G的各顶点的最短路径长度。

关于松弛,个人感觉就是缩小路径上权值的上界。

负环的操作及判断,将在文末给出。

关于优化,将会在以后更新小讲一下。

下面来一个用邻接矩阵实现的模版吧,注意一下初始化和松弛操作

void SPFA(int s)  
{  
    for(int i = 0; i < n; i ++)  
        dis[i] = INF;  
    vis[s] = true;  
    dis[s] = 0;  
      
    queue<int> q;  
    q.push(s);  
    while(!q.empty())  
    {  
        int cur = q.front();  
        q.pop();  
        vis[cur] = false;  
        for(int i = 0; i < n; i ++)  
        {  
            if(dis[cur] + map[cur][i] < dis[i])  
            {  
                dis[i]=dis[cur] + map[cur][i];  
                if(!vis[i])  
                {  
                    q.push(i);  
                    vis[i] = true;  
                }  
            }             
        }  
    }  
}


相信看完图解,可以很容易地理解。下面我们来看一些题目

第一题

POJ 2387 Til the Cows Come Home http://poj.org/problem?id=2387


本题就是很普通的最短路了,输入n, m,求1 到 n 的最短的距离,你可以用其它方法A掉,但是今天我们用一下spfa, 还有就是这几天看了好久的邻接表,所以这题就用邻接表来吧,过几天,会写一篇关于邻接表的。

#include<stdio.h>
#include<queue>
#include<string.h>
#include<algorithm>
using namespace std;

#define N 2005
#define INF 1 << 29

struct Edge
{
    int to, next, dis;
}edge[2 * N];

int head
;
int d
;
bool vis
;
int n, m;
int k;

void init()
{
    memset(head, -1 , sizeof(head));
    k = 1;
}

void addedge(int s, int t, int dist)    // 用邻接表实现图的存储
{
    edge[k].to = t;
    edge[k].dis = dist;
    edge[k].next = head[s];
    head[s] = k ++;
}

int spfa(int s)  // spfa 操作,如有问题,可以参看上一篇的spfa图解
{
    queue<int> que;
    for(int i = 1; i <= n; i ++)
        d[i] = INF;
    d[s] = 0;
    memset(vis, 0, sizeof(vis));
    que.push(s);
    while(!que.empty())
    {
        int t = que.front();
        que.pop();
        vis[t] = false;
        for(int i = head[t]; i != -1; i = edge[i].next)
        {
            int v = edge[i].to;   //  起点
            int w = edge[i].dis;  //  距离
            if(d[v] > d[t] + w)
            {
                d[v] = d[t] + w;
                if(!vis[v])    // 若该点未在队列中,将改点拉入队列
                {
                    que.push(v);
                    vis[v] = true;
                }
            }
        }
    }
    return d
;             // 返回1 到 n 的距离,即本题的答案
}

int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        int a, b, c;
        init();
        for(int i = 1; i <= m; i ++)
        {
            scanf("%d%d%d",&a,&b,&c);
            addedge(a, b, c);   // 无向图,首尾都来一次
            addedge(b, a, c); 
        }
        printf("%d\n", spfa(1));
    }
    return 0;
}


关于负环的操作

下面给个 “所谓的模版” ,具体还是要看题意,也要注意各种初始化的问题

struct edge
{
    int to, next, dis;
}e[MAX];

bool spfa()  
{  
    for(int i = 0; i <= n; i ++)  
        dis[i] = INF;  

    queue<int> q;  
    dis[0] = 0;  
    vis[0] = true;  
    cnt[0] = 1;  
    q.push(0);  
  
    while(!q.empty())  
    {  
        int cur = q.front();  
        q.pop();  
        vis[cur] = false;  
  
        for(int i = head[cur]; i != -1; i = e[i].next)  
        {  
            int id = e[i].to;  
            if(dis[cur] + e[i].dis > dis[id])  
            {  
                dis[id] = dis[cur] + e[i].dis;  
                if(!vis[id])  
                {  
                    cnt[id] ++;  
                    if(cnt[cur] > n)  
                        return false;  
                    vis[id] = true;  
                    q.push(id);  
                }  
            }  
        }  
    }  
    return true;  
}


练习(你可以尝试各种最短路的算法,提高对算法的熟练度)

HDU 1874 畅通工程续 http://acm.hdu.edu.cn/showproblem.php?pid=1874
POJ 1201 Intervals http://poj.org/problem?id=1201
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: