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

     在家的学习效率实在太低,想想暑假都要过完了,什么东西都没学到。

     如果你还没理解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[N];
int d[N];
bool vis[N];
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[n];             // 返回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
  

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值