最短路的幾種算法及其優化(模板)


一.Dijkstra 算法

    dijkstra算法適用於邊權為正的情況,求單源最短路,適用於有向圖和無向圖

    模板偽代碼:

                  清除所有點的標號

                  設d[0]=0,其余d[i]=INF;

                  循環n次{

                             在所有未標記的節點中,尋找d[i]最小的點x

                             給x做標記

                            對於從x出發的所有邊(x,y)更新d[y]=min(d[y],d[x]+w[x,y]);

                             }

                  

memset(v,0,sizeof(v));
for(int i=0;i<n;++i)
d[i]=(i==0?0:INF);
for(int i=0;i<n;++i)
{
    int x,m=INF;
    for(int j=0;j<n;++j)
    if(!visit[j]&&d[j]<m)
    {
        m=d[j];
        x=j;
    }
    visit[x]=1;
    for(int j=0;j<n;++j)
    d[j]=min(d[j],d[x]+w[x][j]);
 } 

     簡單說一下dijkstra的優化:

              1.儲存結構上:鄰接矩陣是很占空間的(眾所周知),所以我們一般采用鄰接表或者邊表

              2.堆優化:因為在dijkstra中有循環n次尋找最小dict的過程,我們可以維護一個小根堆來實現,也就把時間復雜度從n^2降到了n*(logn+E)。

     優化后的dijkstra,自己寫的:

 

/*
建圖用的鄰接表,復雜度O(E*logE)
*/

struct pnode {
    int num;
    int len;

    pnode() {}
    pnode(int a, int b) : num(a), len(b) {}//初始化結構體用的,把a復制給num,把b復制給len; 
    bool operator < (const pnode tmp) const {
        return len > tmp.len;
    }
};

int dis[N];
bool vis[N];
int n;

void dijkstra(int s) {
    priority_queue<pnode> q;
    q.push(pnode(s, 0));
    pnode u;
    int v, i, res = inf;
    for(i = 0; i <= n; ++i) dis[i] = inf, vis[i] = false;
    dis[s] = 0;

    while(!q.empty()) {
        u = q.top(); q.pop();
        if(u.len != dis[u.num]) continue;/*這是應對優先隊列中的重復入隊的點,只要最新的那個點就可以了*/
        if(vis[u.num])  continue;
        vis[u.num] = true;

        for(i = head[u.num]; i != -1; i = g[i].next) {
            v = g[i].to;
            if(dis[v] > u.len + g[i].val) {
                dis[v] = u.len + g[i].val;
                q.push(pnode(v, dis[v]));
            }
        }
    }
}

二.Bellman-Ford的優化(也就是SPFA,直接看三吧)

三.SPFA模板及SPFA的優化

   1.普通SPFA模板(隊列化的Bellman-Ford算法):

int visit[N],dis[N];
bool SPFA(int s)
{
    queue<int>q;
    memset(dis,127,sizeof(dis));
    memset(visit,false,sizeof(visit));
memset(cnt,0s,sizeof(cnt)); dis[s]
=0; visit[s]=true; q.push(s); while(!q.empty()) { int k=q.front(); q.pop(); visit[k]=false; for(int i=head[k];i;i=edge[i].last)/*邊表*/ { if(dis[k]+edge[i].w<dis[edge[i].v]) { dis[edge[i].v]=dis[k]+edge[i].w; if(!visit[edge[i].v]) { q.push(edge[i].v); visit[edge[i].v]=true;
if(++cnt[edge[i].v]>n) /*如果某一個點的入隊次數超過了n次,說明存在負環,返回false*/
return false; } } } } return true;/*安全跑完了,不存在環*/ }

    2.SPFA的優化

      SPFA算法有兩個優化策略SLF和LLL——SLF:Small Label First 策略,設要加入的節點是j,隊首元素為i,若dist(j)<dist(i),則將j插入隊首,否則插入隊尾; LLL:Large Label Last 策略,設隊首元素為i,隊列中所有dist值的平均值為x,若dist(i)>x則將i插入到隊尾,查找下一元素,直到找到某一i使得dist(i)<=x,則將i出隊進行松弛操作。 SLF 可使速度提高 15 ~ 20%;SLF + LLL 可提高約 50%。 在實際的應用中SPFA的算法時間效率不是很穩定,為了避免最壞情況的出現,通常使用效率更加穩定的Dijkstra算法。實際上dijkstra算法+heap優化后是一定快於一般SPFA的,而且更加穩定。

1)SPFA的SLF優化,(很簡單的,只要使用雙端隊列就可以實現了)。

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <deque>
using namespace std;
const int N=501;
const int NN=100001;
const int inf=0x7fffffff;
int n,nu;
typedef struct node
{
    int adj,val;
    struct node *next;
};
node node[NN],*p[N];
int SPFA()
{
    deque<int> qu;
    int x,i,a,b;
    int vis[N],dis[N],num[N];
    struct node *head[N];
    for(i=1;i<=n;i++)
    {
        vis[i]=0;
        num[i]=0;
        dis[i]=inf;
        head[i]=p[i];
    }
    dis[1]=0;
    vis[1]=1;
    num[1]++;
    qu.push_back(1);
    while(!qu.empty())
    {
        x=qu.front();
        qu.pop_front();
        vis[x]=0;
        head[x]=p[x];
        while(head[x])
        {
            a=head[x]->adj;
            b=head[x]->val;
            if(dis[a]>dis[x]+b)
            {
                dis[a]=dis[x]+b;
                if(!vis[a])
                {
                    vis[a]=1;
                    num[a]++;
                    if(num[a]>=n)
                        return 1;
                    if(!qu.empty())
                    {
                        if(dis[a]>dis[qu.front()])
                            qu.push_back(a);
                        else
                            qu.push_front(a);
                    }
                    else
                        qu.push_back(a);
                }
            }
            head[x]=head[x]->next;
        }
    }
    return 0;
}
int main()
{
    int t,i,m,w,a,b,c;
    scanf("%d",&t);
    while(t--)
    {
        memset(node,0,sizeof(node));
        memset(p,0,sizeof(p));
        nu=0;
        scanf("%d%d%d",&n,&m,&w);
        for(i=0;i<m;i++)
        {
            scanf("%d%d%d",&a,&b,&c);
            node[nu].adj=b;
            node[nu].val=c;
            node[nu].next=p[a];
            p[a]=&node[nu];
            nu++;
            node[nu].adj=a;
            node[nu].val=c;
            node[nu].next=p[b];
            p[b]=&node[nu];
            nu++;
        }
        for(i=0;i<w;i++)
        {
            scanf("%d%d%d",&a,&b,&c);
            node[nu].adj=b;
            node[nu].val=-c;
            node[nu].next=p[a];
            p[a]=&node[nu];
            nu++;
        }
        if(SPFA())
            puts("YES");
        else
            puts("NO");
    }
    return 0;
}
網上找的
#include<iostream>
using namespace std;
#include<deque>
#include<cstdio>
#include<cstring>
int n,m;
#define N 1001
struct Edge{
    int u,v,w,last;
}edge[N];
int head[N];
int dict[N];
bool visit[N];
void input()
{
    scanf("%d%d",&n,&m);/*n個點,m條邊*/
    for(int i=1;i<=m;++i)
    {
        scanf("%d%d%d",&edge[i].u,&edge[i].v,&edge[i].w);
        edge[i].last=head[edge[i].u];
        head[edge[i].u]=i;
    }    
}
bool SPFA()
{
    deque<int>q;
    memset(dict,127,sizeof(dict));
    memset(visit,false,sizeof(visit));
    dict[1]=0;
    visit[1]=true;;
    int cnt[N];
    memset(cnt,0,sizeof(cnt));/*判斷有無環的標志*/
    ++cnt[1];
    while(!q.empty())
    {
        int k=q.front();
        q.pop_front();
        visit[k]=false;/*取出后,不要忘記標志位*/
        for(int l=head[k];l;l=edge[l].last)/*邊表*/
        {
            int p=edge[l].v;
            if(dict[p]>dict[k]+edge[l].w)
            {
                dict[p]=dict[k]+edge[l].w;
                ++cnt[p];
                if(cnt[p]>n) return true;/*如果某個點的入隊次數超過了n,那么一定存在環*/
                if(!visit[p])
                {
                    visit[p]=true;
                    if(!q.empty())/*這就是SLF Small Label First 策略.的核心,把將要入隊的元素的dict與隊首元素相比較,如果將要入隊的元素的dict大的話,就放在隊尾,否則就放在隊首 */
                    {
                        if(dict[p]>dict[q.front()])/*這樣可以保證始終用dict小的更新,也節約了時間*/
                        q.push_back(p);
                        else q.push_front(p);/*所以必須使用雙端隊列*/
                    }
                    else q.push_back(p);/*不要忘記考慮隊列為空的情況*/
                }
            }
        }
    }
    return false;
}
int main()
{
    input();
    if(SPFA())
    printf("circle");
    else{
        for(int i=1;i<=n;++i)
        printf("%d ",dict[i]);
    }
    return 0;
}
自己打了一遍,還可以理解

2.SPFA的LLL優化

   在網上實在沒找到可靠的。

3.SPFA的DFS優化:

   在很多的題目中,SPFA都是用BFS來實現的,對於隨機圖來說,BFS的速度會遠大於DFS,但是對於某些特殊的圖結構來說,DFS也是一個很好的選擇

    例如 1):題目中要求在最短的時間內,判斷有無環,DFS明顯會比BFS快(例題是POj上一個判斷單詞接龍的題目)

          2):對於網格型的圖,DFS的速度比BFS快

          模板:  

void SPFA(int k)
{
    flag[k]=true;
    for(int l=head[k];l;l=edge[l].last)
    {
        int v=edge[l].v;/*找出第一個可以被更新的點*/
        if(dis[v]>dis[k]+edge[l].w)
        {
            dis[v]=dis[k]+edge[l].w;
            if(!flag[v])
            {
                SPFA(v);/*接着深搜下去*/
            }
            else /*這表明從某個點開始DFS,結果又搜到了這個點,說明存在負權回路*/
            {
                printf("cycle");
                return;
            }
        }
    }
    flag[k]=false;/*不要忘記再把k設為false,為的是讓他能夠重復入隊*/
}

 

四,Floyd算法模板(沒有任何優化,就是鄰接矩陣和n^3,一般情況單源最短路是絕對不會用的)

for(int k=1;k<=n;++k)/*注意這個k必須在最外層循環才行*/

          for(int i=1;i<=n;++i)

             for(int j=1;j<=n;++j)

              dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]); 

 

  


免責聲明!

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



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