Overview and graph theory SPFA

Overview and graph theory SPFA

2019-12-28

Powered by Gauss

1. Graph Theory - Shortest

Graph theory is an integral part of the learning process Informatics. Application of graph theory is very broad, in real life, everyone everywhere can be met, such as electronic map, ticket inquiries.

Examination of the scope of the algorithm contest now is immense, but the main test sites is graph theory, DP, number theory, strings and so on. Graph theory is a big test sites, such as:

topic source
Optimal Trade          NOIP2009 improve the Group III title
Information transfer NOIP2015 improve the Group II title
Transport plan NOIP2015 improve the group sixth title
Finding your way NOIP2014 improve the group fifth title

 

 

 

 

Of course, the algorithm on graph theory is much more than that, just to name a few famous above topics.

[History of graph theory]

 

Graph theory originated in a very classic problem - Konigsberg (Konigsberg) problem.

 

In 1738, the Swedish mathematician Euler (Leornhard Euler) to solve the problem of Konigsberg. Thus the birth of graph theory. Euler also became the founder of graph theory.

 

In 1859, the British mathematician Hamilton invented a game: with a regular solid dodecahedron, which marked the apex of 20 world-famous 20 cities require a player to find each vertex along each side by just a closed loop, the "detour world."
 
The language of graph theory, the aim of the game is to find a generating circle in Figure dodecahedron. This generated circle came to be known Hamilton circuit. The problem was later called the Hamilton problem. Because operations research, computer science and a lot of problems in coding theory can be turned into Hamilton problem, causing widespread attention and study.
 
[Branch of graph theory]
 
As shown above, FIG theory mainly divided into the minimum spanning tree, and the shortest maximum flow. One of the most important is the shortest.
There are many associated with the shortest path algorithm, the following diagram shows several main algorithm.
Shortest path algorithm []
 
As shown above, the shortest path algorithm is divided into Floyd-Warshall, Bellman-Ford, SPFA, Dijsktra.
These types of algorithms different, the following table shows the comparison of them:
  Floyd Dijsktra Bellman-Ford SPFA
Space complexity O (N 2 )            

MAN)

MAN) MAN)
time complexity O(N3) O((M+N)logN) O(NM) O(NM)
适用情况 稠密图 稠密图 稀疏图 稀疏图
负权 Y N Y Y
有负权边 Y N Y Y
判断负权回路 N N Y Y
今天我们主要来研究SPFA算法。
 
 
-------------------------------------------------------------------------- 不怎么华丽的分割线---------------------------------------------------------------------
 
首先,我们用一道题来引入SPFA的思想。
题目传送门:HDU-2544
大家都看过这道题了。
这是一道模板题,用上述的四个算法都能过(逃~)
 
名称 时间
Floyd-Warshall 899MS
Dijsktra 256MS
Bellman-ford 159MS
SPFA 43MS
速度上的差距无法弥补,所以还是要学SPFA。
 
【SPFA】简介
SPFA 算法是 Bellman-Ford算法 的队列优化算法的别称,通常用于求含负权边的单源最短路径,以及判负权环。SPFA 最坏情况下复杂度和朴素 Bellman-Ford 相同,为 O(VE)。(源自百度百科)
SPFA的全称是Shortest Path Faster Algorithm,即求最短路更快的方法,源自Bellman-Ford。
【SPFA】算法思想
SPFA与贝尔曼福特算法最大的区别就是存储方式。
下面给出Bellman的代码和SPFA的代码,以更好的展示区别
void spfa(int s,int n)
{
    int r=0,l=0;
    memset(dl,0,sizeof(dl));
    memset(b,1,sizeof(b));
    memset(dis,0x7f7f7f7f,sizeof(dis));
    dis[s]=0;
    dl[r++]=s;
    while(l<r)
    {
        int x=dl[l];
        b[x]=1;
        for(int i=1;i<=n;i++)
        {
            if(a[x][i]!=0x7f7f7f7f)
            {
                if(dis[i]>dis[x]+a[x][i])
            {
                dis[i]=dis[x]+a[x][i];
                if(b[i])
                {
                    dl[r++]=i;
                    b[i]=0;
                }
            }
            }
        }
        l++;
    }
}
void bellman()
{
    int s=1;
    int d[NUM];
    for(int i=1;i<=n;i++) d[i]=INF;
    d[s]=0;
    for(int k=1;k<=n;k++)
    {
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=n;j++)
            {
                if(d[j]>d[i]+graph[i][j]) d[j]=d[i]+graph[i][j];        
            }
        } 
    }
    printf("%d",d[n]);
}

由此可以看出,bellman和SPFA的代码最大的区别就是“松弛”方式。

松弛的步骤如下所示:

        

from to to to to
1 2 3 4 5
INF 5 2 5 40


 

∵ 2+3+1<40

from to to to to
1 2 3 4 5
INF 5 2 5 6

 

 

 

仔细观察两张表格的区别,得出松弛操作的表达式:

if(d[j]>d[i]+graph[i][j]) d[j]=d[i]+graph[i][j];

其中d[ j ]表示从1号点到 j 号点距离。

【SPFA】的计算过程

SPFA的思想很像BFS:

使用队列来实现:

1.起点s入队,计算s所有邻居到s得距离。把s出队,状态有更新的邻居入队,没更新的出队;

2.现在的队头就是s的一个邻居p,弹出p,重复步骤1、2;

PS:这时某个点可能因为多次修改而对此入队,这时将这个点u入队就行了。

下面给出SPFA的HDU2544的完整代码:

#include<bits/stdc++.h>
using namespace std;
const int INF=1e6;
const int NUM=105;
struct edge
{
    int from,to,w;
    edge(int a,int b,int c)
    {
        from=a;
        to=b;
        w=c;
    }
};
vector<edge>e[NUM];
int n,m;
int pre[NUM];
int spfa(int s)
{
    int dis[NUM];
    bool inq[NUM];
    int Neg[NUM];
    memset(Neg,0,sizeof(Neg));
    Neg[s]=1;
    for(int i=1;i<=n;i++) 
    {
        dis[i]=INF;
        inq[i]=false;
    }
    dis[s]=0;
    queue<int>Q;
    Q.push(s);
    inq[s]=true;
    while(!Q.empty())
    {
        int u=Q.front();
        Q.pop();
        inq[u]=false;
        for(int i=0;i<e[u].size();i++)
        {
            int v=e[u][i].to,w=e[u][i].w;
            if(dis[u]+w<dis[v])
            {
                dis[v]=dis[u]+w;//松弛 
                pre[v]=u;//记录路径 
                if(!inq[v])//更新队列 
                {
                    inq[v]=true;//更新队列的元素
                    Q.push(v);//加入队列
                    Neg[v]++;//判断入队次数
                    if(Neg[v]>=n) return 1;//判断负环 
                }
            }
        }
    }
    printf("%d\n",dis[n]);
    return 0;
}
int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        if(n==0 && m==0) return 0;
        for(int i=1;i<=n;i++) e[i].clear();
        while(m--)
        {
            int a,b,c;
            scanf("%d%d%d",&a,&b,&c);
            e[a].push_back(edge(a,b,c));
            e[b].push_back(edge(b,a,c));
        }
        spfa(1);
    }
    return 0;
} 

我们来详细解读一下上面的代码;

首先,因为题目的要求是无向图,所以要双向存储:

e[a].push_back(edge(a,b,c));
e[b].push_back(edge(b,a,c));

然后,我们从一号点开始进行SPFA计算

spfa(1);

紧接着,

int dis[NUM];
bool inq[NUM];
int Neg[NUM];
memset(Neg,0,sizeof(Neg));
Neg[s]=1;

其中dis用来存储距离,inq用来存储这个点是否在队列里,Neg用来存储负环。

Neg[s]=1;
for(int i=1;i<=n;i++) 
{
    dis[i]=INF;
    inq[i]=false;
}

这一段代码是初始化,将两个数组进行初始化,非常重要。

dis[s]=0;
queue<int>Q;
Q.push(s);
inq[s]=true;

这里就是SPFA的精华部分,我们使用队列存储。

while(!Q.empty())

只要队列不为空,就说明有点需要松弛

int u=Q.front();
Q.pop();
inq[u]=false;
for(int i=0;i<e[u].size();i++)
{
    int v=e[u][i].to,w=e[u][i].w;
    if(dis[u]+w<dis[v])
    {
        dis[v]=dis[u]+w;//松弛 
        pre[v]=u;//记录路径 
        if(!inq[v])//更新队列 
        {
            inq[v]=true;//更新队列的元素
            Q.push(v);//加入队列
            Neg[v]++;//判断入队次数
            if(Neg[v]>=n) return 1;//判断负环 
        }
    }
}

这里就是图论算法求最短路的精华,松弛;

今天就讲到这里,再见!!!

 

Guess you like

Origin www.cnblogs.com/Warframe-Gauss/p/12111611.html