caioj1088·SPFA演算法模板題·最短路
阿新 • • 發佈:2019-01-25
求距離一般有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;
}
(直接在網站複製過來了,,懶得打了。。。