1. 程式人生 > >關於最短路徑演算法的一些小結(Dijkstra,floyd,spfa)

關於最短路徑演算法的一些小結(Dijkstra,floyd,spfa)

前言
最近幾天,學習完了一些最短路徑演算法,由於學習路程艱難曲折,所以寫下了這篇部落格來總結一下
———————————————————————————————————————————————————————————

正文

floyd
首先,提到最短路徑演算法,最好寫的肯定是Floyd演算法,如果圖的點數沒有超過300的話,一般的題目還是能卡過去的
floyd演算法,主要是通過列舉中轉點(其實就是每個點),對全圖都實現鬆弛操作,其實本質還是dp的思路,由於其較為簡單,此處便不再進行的過多的講解
附上程式碼:

void floyd()
{
    for (int k=1; k<=n; k++)
        for (int i=1; i<=n; i++)
            for (int j=1; j<=n; j++)
                dist[i][j]=min(dist[i][j],dist[i][k]+dist[k][j]);//dist[i][j]陣列表示全圖中i,j兩點的距離;鬆弛操作
}

floyd其實還可以尋找最小環,只需將dist陣列初始化時改為無窮大,dist[i][i]設為無窮大,當跑完floyd時,
掃一遍,min=min(dist[i][i],min);即可

spfa(Shortest Path Faster Algorithm)
關於spfa———它死了
如果經(shen)常(shou)做(qi)題(hai)的同學會知道,如果是國內的比賽,
80%是會卡掉spfa的~~(畢竟不卡不舒服碼)~~,如NOI2018Day1T1歸程 ,
甚至還有hdu4889專門造資料卡掉spfa,所以對於spfa還是慎用。
spfa演算法其實是Bell-Ford演算法的佇列優化,如果不是特意造資料卡的話,還是挺快的
對於spfa其演算法本質是:
設立一個先進先出的佇列q用來儲存待優化的結點,優化時每次取出隊首結點u,並且用u點當前的最短路徑估計值對離開u點所指向的結點v進行鬆弛操作,如果v點的最短路徑估計值有所調整,且v點不在當前的佇列中,就將v點放入隊尾。這樣不斷從佇列中取出結點來進行鬆弛操作,直至佇列空為止。
下面附上程式碼:

void spfa()
{
     queue<int>q;//佇列
     while(!q.empty())
     q.pop();
     q.push(s);
     vi[s]=1;//已經被訪問過,標記一下
     int sp,t;
     while(!q.empty())
     {
               sp=q.front();
               q.pop();
               vi[sp]=0;
               for(int i=head[sp];i;i=e[i].next)
               {
                       if(dis[e[i].t]>dis[sp]+e[i].w)//類似於Floyd的鬆弛操作
                       {
                                  dis[e[i].t]=dis[sp]+e[i].w;
                                  if(vi[e[i].t]==0)
                                  {
                                            q.push(e[i].t);//入隊,用此節點作為中轉點繼續鬆弛
                                            vi[e[i].t]=1;
                                  }
                       }
               }
     }
}

段凡丁論文中的複雜度證明 (O(kE),k 是小常數)是錯誤的,所以該演算法的最壞複雜度其實是 O(VE)。
但是Spfa並不是一無所用,對於存在負邊權的圖(非三角網格圖)還是挺快的;

Dijkstra
可以這麼來說,Dijktra應該是目前最穩的演算法,對於不用堆優化的時間複雜度為O(n^2),但是用Dijkstra不用堆優化,
就失去存在的意義了,Dijktra演算法,其實本質上還是一個BFS,他使用BFS解決賦權有向圖或者無向圖的單源最短路徑問題,演算法最終得到一個最短路徑樹。
Dijktra演算法的大致思路:
把源點放入優先佇列(堆)中,然後再把所有其連出去的邊所到的終點,利用這個終點再對與他間接相連的點進行鬆弛,直到優先佇列為空為止,再把所有進隊的點都標為已訪問過,確保不多次進隊(spfa被卡是因為其將很多點多次入隊),但對於存在負邊權的圖,
Dijktra就沒有用武之地了,
下面附上程式碼:

struct Node
{
	int id, d; 
	bool operator < (const Node &b)const//過載運算子
	{
		return d > b.d;
	}
};
void dijkstra(int s)
{
	for (int i=1; i<=n; i++)
		dis[i]=inf,vis[i]=0;
		
	dis[s] = 0;
	priority_queue <Node> q;
	q.push((Node){s, 0});
	while (!q.empty())
	{
		Node now=q.top();
		q.pop();
		if (vis[now.id])
			continue;
		vis[now.id]=1;
		for (int i=0;i<v[now.id].size();i++)
		{
			//now.id---c[now.id][i]--->v[now.id][i]
			if (dis[now.id]+c[now.id][i]<dis[v[now.id][i]])
			{
				dis[v[now.id][i]]=dis[now.id]+c[now.id][i];
				q.push((Node){v[now.id][i],dis[v[now.id][i]]});
			}
		}
	}
}

好了,單源最短路徑就先談到這裡,希望大家多多做題以達到能熟練(7分鐘以內)敲完,見題就秒的熟練程度,
祝大家 noip2018RP++,2019NOI++

不喜勿噴