概要とグラフ理論SPFA

概要とグラフ理論SPFA

2019年12月28日

ガウスによって供給

1.グラフ理論 - 最短

グラフ理論は、学習プロセス情報の不可欠な部分です。グラフ理論の適用は、実際の生活の中で、どこでも、誰もがこのような電子地図、チケットのお問い合わせなど、満たすことができる、非常に広いです。

アルゴリズムコンテストの適用範囲の検討は今広大ですが、メインのテストサイトは、グラフ理論、DP、数論、文字列などです。グラフ理論は、以下のような大規模テストサイト、次のとおりです。

タイトル ソース
最適なトレード          グループIIIのタイトルを改善NOIP2009
情報伝達 グループIIのタイトルを改善NOIP2015
交通計画 グループ第タイトルを改善NOIP2015
あなたの方法を見つけます グループ第五のタイトルを改善NOIP2014

 

 

 

 

もちろん、グラフ理論上のアルゴリズムは、わずか数有名上記のトピックに名前を付けるために、はるかにそれよりもです。

[グラフ理論の歴史]

 

ケーニヒスベルグ(ケーニヒスベルク)問題 - グラフ理論は非常に古典的な問題で始まりました。

 

1738年には、スウェーデンの数学者オイラー(Leornhardオイラー)はケーニヒスベルクの問題を解決します。したがって、グラフ理論の誕生。オイラーはまた、グラフ理論の創始者となりました。

 

1859年、英国の数学者ハミルトンはゲームを発明:20世界的に有名な20都市の頂点をマークし、通常の固体十二面体、とだけして、各辺に沿って各頂点を見つけるために、プレーヤーが必要です閉ループ、「迂回世界。」
 
グラフ理論の言語は、ゲームの目的は、図十二面体で生成サークルを見つけることです。この生成された円は、ハミルトン回路を知られるようになりました。問題は、後にハミルトンの問題と呼ばれていました。操作は、コンピュータサイエンスを研究し、符号理論における多くの問題が広く注目して研究を引き起こし、ハミルトンの問題に変換することができますので。
 
[グラフ理論の支店]
 
上記のように、図の理論は、主に最小スパニングツリー、および最短の最大流に分割されます。最も重要なものの1つは、最短です。
最短パスアルゴリズムは、次の図が示すいくつかの主要なアルゴリズムに関連する多くがあります。
最短パスアルゴリズム[]
 
上記のように、最短パスアルゴリズムは、フロイド・ウォーシャル、ベルマン - フォード、SPFA、Dijsktraに分割されています。
アルゴリズムのこれらのタイプの異なる、以下の表に示すそれらの比較:
  フロイド Dijsktra ベルマン・フォード SPFA
宇宙複雑 O(N 2)            

O(M)

O(M) O(M)
時間複雑 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;//判断负环 
        }
    }
}

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

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

 

おすすめ

転載: www.cnblogs.com/Warframe-Gauss/p/12111611.html