圖論基本算法


圖論是NOIP必考的知識點。

松弛操作

如圖:

比如說從1到2可以有2種解法,一種是直接走,另一種就是用一個點來中轉;

從這兩條路上選最短的走法的操作就叫松弛。

根據這個操作啊就可以做出像暴力一樣的最短路算法————Floyd算法.

我們可以先初始化把不相連的邊都設為無窮大,再不斷進行松弛操作不斷更新最短路。

這樣就可以得出所有的兩點之間的最短路,還能處理負邊權。

不過就是有點慢時間復雜度是O(n3

for(k=1;k<=n;k++) //中轉點
  for(i=1;i<=n;i++) 
    for(j=1;j<=n;j++) 
      if(dis[i][j]>dis[i][k]+dis[k][j]) //松弛操作
        dis[i][j]=dis[i][k]+dis[k][j];

但是該算法適用於求解多源最短路徑,所以時間復雜度大也是正常的。

而單源最短路徑主要有兩種

Dijkstra算法O(n2)加堆優化O(nlogn)

用來計算從一個點到其他所有點的最短路徑的算法。

Dijkstra它不能處理存在負邊權的情況。

算法描述:

       設起點為sdis[v]表示從sv的最短路徑,

       a)初始化:dis[v]=(vs); dis[s]=0;;

       b)For (i = 1; i <= n ; i++)

            1.在沒有被訪問過的點中找一個頂點u使得dis[u]是最小的。(可以認為是貪心操作)

            2.u標記為已確定最短路徑的點

            3.與u相連的每個沒有被確定最短路徑的頂點進行松弛操作。

算法思想:我們把點分為兩類,一類是已確定最短路徑的點,稱為“白點”,另一類是未確定最短路徑的點,稱為“藍點”。如果我們要求出一個點的最短路徑,就是把這個點由藍點變為白點。從起點到藍點的最短路徑上的中轉點在這個時刻只能是白點。

    Dijkstra的算法思想,就是一開始將起點到起點的距離標記為0,而后進行n次循環,每次找出一個到起點距離dis[u]最短的點u,將它從藍點變為白點。隨后枚舉所有的藍點vi,如果以此白點為中轉到達藍點vi的路徑dis[u]+w[u][vi]更短的話,這將它作為vi的“更短路徑”dis[vi](此時還不確定是不是vi的最短路徑)。

就這樣,我們每找到一個白點,就嘗試着用它修改其他所有的藍點。中轉點先於終點變成白點,故每一個終點一定能夠被它的最后一個中轉點所修改,而求得最短路徑。

例題:

luogu p[3371]

#include<iostream>
#include<cstdio>
#include<algorithm> 
using namespace std;
bool b[500010];
long long dis[500010],lin[500010],tot,n,m,s;
struct cym{
    int from,to,len,next;
}e[2000010];
int main()
{
    scanf("%d%d%d",&n,&m,&s);
    for(int i=1;i<=n;i++)
    dis[i]=2147483647;
    for(int i=1;i<=m;i++)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        e[++tot].from=a;
        e[tot].to=b;
        e[tot].len=c;
        e[tot].next=lin[a];
        lin[a]=tot;
    }
    dis[s]=0;
    for(int i=1;i<=n;i++)
    {
        int minn=2147483647;
        int k=0;
        for(int j=1;j<=n;j++)
          if(minn>dis[j]&&!b[j])
            {
              minn=dis[j];
               k=j;    
            }
        b[k]=1;
        for(int j=lin[k];j;j=e[j].next)
          if(dis[e[j].to]>dis[k]+e[j].len)
            dis[e[j].to]=dis[k]+e[j].len;
    }
    for(int i=1;i<=n;i++)
    {
        printf("%lld ",dis[i]);
    }
}

除了這種算法,還有兩個思想相同但速度不一樣的算法。

一個是SPFA,一個是Bellman_ford算法。

這兩種算法的思想都一樣,但是SPFA是有隊列優化的,所以介紹SPFA算法。

也是一個單源最短路徑算法,但是不同的是他的速度一般是要比dijkstra要快的,且它可以處理負邊權,甚至還可以判負環,但是容易被卡,所以如果在比賽中時間真的充足的話,還是建議寫堆優化dijkstra。

 算法描述:

       設起點為s,dis[v]表示從s到v的最短路徑,vis[i]數組表示i是否在隊中。

       a)初始化:dis[v]=∞(v≠s); dis[s]=0;

   將s入隊,vis[i]=1.

       b)while(!q.empty())

            1.取出隊首u,並將vis數組設為零。

            2.與u相連的每個沒有被確定最短路徑的頂點進行松弛操作。

            3.如果被確定最短路徑的頂點沒有在隊中,入隊。

算法思想:動態逼近法

設立一個先進先出的隊列用來保存待優化的結點,優化時每次取出隊首結點u,並且用u點當前的最短路徑估計值對離開u點所指向的結點v進行松弛操作,

如果v點的最短路徑估計值有所調整,且v點不在當前的隊列中,就將v點放入隊尾。這樣不斷從隊列中取出結點來進行松弛操作,直至隊列空為止。

代碼(題目同上):

 

#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;
queue<long long>q;
long long v[1000010],minn[1000100];
long long n,m,s,lin[1000010],tot=0;
struct min_road{
    long long from,to,next,len;
}e[1000010];
void add(long long f,long long t,long long l)
{
    e[++tot].from=f;
    e[tot].to=t;
    e[tot].len=l;
    e[tot].next=lin[f];
    lin[f]=tot;
}
int main()
{

    scanf("%lld%lld%lld",&n,&m,&s);
    for(int i=1;i<=n;i++)minn[i]=2147483647;
    for(int i=1;i<=m;i++)
    {
       long long f,t,l;
        scanf("%lld%lld%lld",&f,&t,&l);
        add(f,t,l);
    }
    q.push(s);
    v[s]=1;
    minn[s]=0;
    while(!q.empty())
    {
       long long cur=q.front();
        q.pop();
        v[cur]=0;
        for(long long i=lin[cur];i;i=e[i].next)
        {
            if(minn[e[i].to]>minn[cur]+e[i].len)
            {
                minn[e[i].to]=minn[cur]+e[i].len;
                if(!v[e[i].to])
                {
                q.push(e[i].to);
                v[e[i].to]=1;
                }

            }
        }
    }
    for(int i=1;i<=n;i++)
    printf("%lld ",minn[i]);
}

 

講完了圖論的最短路算法,還有最小生成樹算法。

如果一個圖有n個點,那么如果有n-1條邊。那么他一定是一棵樹。

反之也成立。

克魯斯卡爾算法即是一種解決最小生成樹的算法。

算法思想:

我們用一種並查集的數據結構,用來判斷該邊是否在生成樹中。

如果要想生成樹最小,即可以貪心將每一條邊的權值都排一下序。

然后逐個判斷是否在樹中,如果沒有就加上,且合並,用並查集維護連通性。

反之就繼續,直到全都判斷完畢或已經出現一棵樹。

代碼(洛谷p3366)

 

#include<bits/stdc++.h>
using namespace std;
int fa[200001];
struct edge{
    int u;
    int v;
    int w;
}e[200001];
int cmp(edge a,edge b)
{
    return a.w<b.w;
}
int find(int x)
{
    return fa[x]==x?x:fa[x]=find(fa[x]);
}
long long cnt=0;
long long ans=0;
long long n,m;
int main()
{

    cin>>n>>m;
    for(int i=1;i<=n;i++)
    fa[i]=i;

    for(int i=1;i<=m;i++)
    cin>>e[i].u>>e[i].v>>e[i].w;

    sort(e+1,e+1+m,cmp);

    for(int i=1;i<=m;i++)
    {
        if(cnt==n-1)
        break;
        int x=find(e[i].u);
        int y=find(e[i].v);
        if(x!=y)
        {
            ans+=e[i].w;
            fa[y]=x;
            cnt++;
        }
    }
    if(cnt!=n-1)
    {
        cout<<"orz";
        return 0;
    }
    cout<<ans;
    return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM