1. 程式人生 > >最短路 dij floy spfa

最短路 dij floy spfa

例題 hdu1874 https://blog.csdn.net/murmured/article/details/18568657

一、Dijkstra

不可以算權值為負數的圖

Dijkstra單源最短路演算法,即計算從起點出發到每個點的最短路。所以Dijkstra常常作為其他演算法的預處理。

 使用鄰接矩陣的時間複雜度為O(n^2),用優先佇列的複雜度為O((m+n)logn)近似為O(mlogn)

為樸素版本:

http://blog.51cto.com/ahalei/1387799

還有一個 堆優化+鄰接表(鏈式前向星)優化版本:

const int maxn=1000005;
const int inf=0x3f3f3f3f;
struct Edge{
	int v,w;//w為距離 
	int next;
};
Edge edge[maxn];//邊編號  從1開始   
struct qnode{    //堆優化 
     int u;	//起點 
     int w;//距離
     
   qnode(int u=0,int w=0):u(u),w(w){}//結構體過載 
  
	bool operator < (const qnode& a) const{
	return w>a.w;
	} 
};
long long dis[maxn];
int head[maxn];
bool vis[maxn];
int x[maxn],y[maxn],z[maxn];
int n,m;
int size; 
void add_edge(int u,int v,int w){//鄰接表加邊 
	  edge[size].v=v;
	  edge[size].w=w;
	  edge[size].next=head[u];
	  head[u]=size;
	  size++; 
}
void dijkstra(int s){
	priority_queue<qnode>q;
	while(!q.empty())
	q.pop();
    q.push(qnode(s,0));
	dis[s]=0;

	while(!q.empty()){
		qnode t=q.top();
		q.pop();
		int u=t.u;
		if(vis[u])continue;
		vis[u]=true;//找到一個點就標記一次 
		for(int i=head[u];i!=-1;i=edge[i].next){
			int v=edge[i].v;
			int w=edge[i].w; 
			if(!vis[v]&&dis[v]>dis[u]+w){
				dis[v]=dis[u]+w;
				q.push(qnode(v,dis[v]));//存到隊堆裡會自動利用堆 進行排序; 
			} 
		}	
	}
}

{鄰接表http://blog.51cto.com/ahalei/1391988 

////其實我咋感覺 領接表 與 鏈式前向星一樣啊

 

二、floyd

可以解決傳遞閉包問題

可以處理邊是負數的情況,判斷圖中是否為有負圈,檢查是否存在dis[i][i]是否為負數

處理迴路(環)就看dis[i][i]。(Floyd 和 bellman-ford 都可已處理環)

任意兩點間的短路問題

 

三、SPFA(bellman-ford)

單源最短路徑    可以判斷負環

bellman-ford演算法的基本思想是,對圖中除了源頂點s外的任意頂點u,依次構造從s到u的最短路徑長度序列dist[u],dis2[u]……dis(n-1)[u],其中n是圖G的頂點數,dis1[u]是從s到u的只經過1條邊的最短路徑長度,dis2[u]是從s到u的最多經過G中2條邊的最短路徑長度……當圖G中沒有從源可達的負權圖時,從s到u的最短路徑上最多有n-1條邊

。因此,dist(n-1)[u]就是從s到u的最短路徑長度,顯然,若從源s到u的邊長為e(s,u),則dis1[u]=e(s,u).對於k>1,dis(k)[u]滿足如下遞迴式,dis(k)[u]=min{dis(k-1)[v]+e(v,u)}.bellman-ford最短路徑就是按照這個遞迴式計算最短路的。

bellman-ford演算法    Bellman-ford 演算法:一個具有n個頂點的圖如果不存在環,則從頂點x,到頂點y,最多經過n-1條邊(要考慮連通性,每個頂點最多經過 1 次),因此 x 到 y 的最短路 最多經過 n - 1 次鬆弛操作(就是更新長度)就應該出現,如果第 n 次鬆弛還可以得到最優,那麼這個圖就肯定是存在環了(直接用Dijkstra 就無法得到最優的,環的存在會影響最優解是否存在)。

SPFA的實現如下:用陣列dis記錄更新後的狀態,cnt記錄更新的次數,佇列q記錄更新過的頂點,演算法依次從q中取出待更新的頂點v,按照dis(k)[u]的遞迴式計算。在計算過程中,一旦發現頂點K有cnt[k]>n,說明有一個從頂點K出發的負權圈,此時沒有最短路,應終止演算法。否則,佇列為空的時候,演算法得到G的各頂點的最短路徑長度。

多次入隊因為因為SPFA沒有向迪傑斯塔拉演算法那樣,尋找dist[]的最小值,所以重複入隊用所有結點來進行鬆弛,更新dis[]的最小值,因為這個點本身dis[]的變化,會影響到與之鄰接的點,所以要重複入隊。

判斷負環程式碼如下( 與不判斷只差兩行)

    bool spfa()    
    {    
        for(int i=0;i<=n;i++)    
            dis[i]=INF;    
        
        bool vis[MAXN]={0};    
        int cnt[MAXN]={0};    
        queue<int> q;    
        dis[0]=0;    
        vis[0]=true;    
        cnt[0]=1;    
        q.push(0);    
        
        while(!q.empty())    
        {    
            int cur=q.front();    
            q.pop();    
            vis[cur]=false;    
        
            for(int i=head[cur];i!=-1;i=e[i].next)    
            {    
                int id=e[i].to;    
                if(dis[cur] + e[i].val > dis[id])    
                {    
                    dis[id]=dis[cur]+e[i].val;    
                    if(!vis[id])    
                    {    
                        cnt[id]++;    
                        if(cnt[cur] > n)    //判斷負環
                            return false;    //結束函式
                        vis[id]=true;    
                        q.push(id);    
                    }    
                }    
            }    
        }    
        return true;    
    }