1. 程式人生 > >caioj1088·SPFA演算法模板題·最短路

caioj1088·SPFA演算法模板題·最短路

求距離一般有Floyd,Dijkstra,Ford,SPFA演算法等

Floyd最簡單也最容易理解
SPFA算是最常用也是解決大部分題目的演算法之一
下面來看一道例題

1088: 最短路(模版 SPFA演算法 元問題 by scy)

時間限制: 1 Sec 記憶體限制: 128 MB
題目描述
【題意】
給出一個圖,起始點是1,結束點是N,邊是雙向的。求點1到點N的最短距離。哈哈,這就是標準的最短路徑問題。
【輸入格式】
第一行為兩個整數N(1≤N≤10000)和M(0≤M≤200000)。N表示圖中點的數目,M表示圖中邊的數目。
下來M行,每行三個整數x,y,c表示點x到點y之間存在一條邊長度為c。(x≠y,1≤c≤10000)
【輸出格式】
輸出一行,一個整數,即為點1到點N的最短距離。
如果點1和點N不聯通則輸出-1。
【樣例1輸入】
2 1
1 2 3
【樣例1輸出】
3

【樣例2輸入】
3 3
1 2 5
2 3 5
3 1 2
【樣例2輸出】
2

【樣例3輸入】
6 9
1 2 7
1 3 9
1 5 14
2 3 10
2 4 15
3 4 11
3 5 2
4 6 6
5 6 9
【樣例3輸出】
20

2/題解

a陣列為鄰接矩陣存圖
d陣列是點到出發點的距離
list為佇列,head,與tail為頭尾指標

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using
namespace std; int a[1100][1100]; int d[11000]; int list[11000],head,tail; bool v[11000]; int n; int main(){ int x,y,c,m,st,ed; scanf("%d%d",&n,&m); memset(a,63,sizeof(a)); for (int i=1;i<=m;i++){ scanf("%d%d%d",&x,&y,&c); if(a[x][y]>c){//可能重複錄入,記錄最小邊打個擂臺
a[x][y]=c; a[y][x]=c; } } //初始化d,後面才有更新的必要 st=1;ed=n; for (int i=1;i<=n;i++) d[i]=999999999; d[st]=0; memset(v,false,sizeof(v));v[st]=true; list[1]=st;head=1;tail=2; while (head!=tail){ x=list[head];//單獨取出 for (int y=1;y<=n;y++){ if(d[y]>d[x]+a[x][y]){//判斷是否能繼續更新 d[y]=d[x]+a[x][y]; if(v[y]==false){//判斷是否已經入隊沒有則... v[y]=true; list[tail]=y; tail++;if (tail==n+1) tail=1;//迴圈陣列,跳轉至隊頭 } } } list[head]=0; head++;if (head==n+1) head=1;//迴圈佇列 v[x]=false; } printf("%d\n",d[n]); }

但上面的演算法只能過40%左右的點

於是做出進一步改進

#include<cstdio>
#include<cstring>
using namespace std;
struct bian//表示有向邊的結構體,構建編目錄
{
    int x,y,d,next;// x表示出發點,y表示終點,next表示和x相連的上一條邊的編號
};

bian a[210000]; int len,last[11000];//a的個數是邊的個數,last的個數是點的個數。

//last[i]表示最後一條和點i相連的邊的編號。
int d[11000];//d[i]表示目前i和出發點的最短距離


void ins(int x,int y,int d)// ins函式的功能是建立一條邊
{
    len++;//全域性增加一條有向邊,len一開始為0
    a[len].x=x; a[len].y=y;a[len].d=d;//邊的賦值
    a[len].next=last[x]; last[x]=len;//邊的聯絡
}

int list[11000],head,tail;//list用來存排隊準備更新其他人的點,head表示頭,tail表示尾
bool v[11000];// v[i]等於true表示點i在佇列list中,等於false表示點i不再佇列list中
int n;

int main()
{
    //freopen("a.in","r",stdin);
    //freopen("a.out","w",stdout);
        int x,y,c,m,st,ed;
        scanf("%d%d",&n,&m);
        len=0;  memset(last,0,sizeof(last));//注意構圖之前一定要初始化,不然後果很嚴重!
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d%d",&x,&y,&c);//題目給出的是無向邊,而我們的邊目錄是有向邊
            ins(x,y,c);//建立正向邊
            ins(y,x,c);//建立反向邊

        }
//1:初始化d,這樣後面才有更新的必要
        st=1;ed=n;
        for(int i=1;i<=n;i++) d[i]=999999999; 
        d[st]=0;//出發點為0
//2:初始化v, 一開始所有點都沒有       
        memset(v,false,sizeof(v)); v[st]=true;//點1作為出發點已經進入list
//3:初始化佇列list
        list[1]=st; head=1;tail=2;//佇列的頭是有人的,佇列的尾tail指的位置是沒人的
        while(head!=tail)//只要頭不等於尾,就表示還有人需要更新別人
        {
            x=list[head];//取出準備好更新別人的人x
            for(int k=last[x];   k   ;   k=a[k].next )//重點理解!k首相等於和x相連的最後一條邊的編號。那麼倒數第二條和x相連的邊的編號是多少呢?在a[最後一條].next可以找到
            {
                y=a[k].y;
                if(d[y]>d[x]+a[k].d)//更新是一定要的
                {
                    d[y]=d[x]+a[k].d;
                    if(v[y]==false)//如果被更新了,那麼要考慮進入佇列(考慮是否去更新別人)
                    {
                        v[y]=true;
                        list[tail]=y;
                        tail++; if(tail==n+1) tail=1;//如果tail超過佇列的最後一個,則變為第一個
                    }
                }
            }
//x已經完成更新別人的任務,就要退出佇列list,那麼需要做什麼呢?
            list[head]=0;
            head++; if(head==n+1) head=1; //如果head超過佇列的最後一個,則變為第一個
            v[x]=false;
        }
        printf("%d\n",d[n]);//最後輸出終點到出發點的距離

    return 0;
}

(直接在網站複製過來了,,懶得打了。。。