Bellman-Ford算法 與 SPFA

Bellman-Ford算法,是單源最短路算法的一種。c++

與以前的 Dijkstra算法 最大的不一樣是:Dijkstra算法沒法判斷含負權邊的圖的最短路,而Bellman-Ford算法能夠處理 存在負權邊 的最短路徑。算法

因爲Bellman-Ford算法簡單地對全部邊進行鬆弛操做,共|V|-1次。因此這個算法的時間效率較低,也正是它的不足之處。性能

Bellman-Ford的時間複雜度: O(V*E) (V,E分別是點數 與 邊數)優化

圖解樣例(來源《算法導論》):code

ay79K0.jpg

僞代碼:blog

INIT(G,s);
for i=1 to |G.V|-1
      for each edge(u,v)∈G.E
            RELAX(u,v,w)
for each edge(u,v)∈G.E
      if v.d>u.d+w(u,v)
            return FLASE
      return TRUE

代碼以下(鄰接矩陣):隊列

#include <bits/stdc++.h>
#define INF 0x3f3f3f3f  
#define MAXN 1010  
int n,m,ori; 
//點,邊,起點  
struct Edge{ int from,to,cost; }edge[MAXN];  
//鄰接矩陣
int dis[MAXN];    
bool Bellman_Ford()
{  
    for(int i=1;i<=n;++i) dis[i]=(i==ori ? 0 : INF);  //初始化
    for(int i=1;i<=n-1;++i)      //n-1
        for(int j=1;j<=m;++j)
        {
        	int u=edge[j].from,v=edge[j].to;
        	if(dis[v]>dis[u]+edge[j].cost)
                dis[v]=dis[u]+edge[j].cost;
        }
    bool flag=1; //判斷是否含有負環
    //原理:負權環能夠無限制的下降總權值,因此若是發現第 n次操做仍可下降總權值,就必定存在負權環。
    for(int i=1;i<=m;++i)
    {
    	int u=edge[i].from,v=edge[i].to;
    	if(dis[v]>dis[u] + edge[i].cost)
        {flag = 0;break;}  
    }
    return flag;
}  
int main()  
{
    std::scanf("%d%d%d",&n,&m,&ori); 
    for(int i=1;i<=m;++i)
        std::scanf("%d%d%d",&edge[i].from,&edge[i].to,&edge[i].cost);  
    if(Bellman_Ford()) //先判斷是否有負環  //這裏也能夠再加一句判斷是否連通,依題而定
        std::printf("%d", dis[n]);
    else  
        std::printf("have negative circle\n");  
    return 0;  
}

SPFA(Shortest Path Faster Algorithm):ci

Dijkstra有隊列優化,Bellman-Ford也有。SPFA是Bellman-Ford的隊列優化,減小了沒必要要的冗餘計算。it

算法流程:用一個隊列來進行維護。初始時將源加入隊列。每次從隊列中取出一個元素,並對全部與他相鄰的點進行鬆弛,若某個相鄰的點鬆弛成功,則將其入隊。直到隊列爲空時算法結束。ast

代碼以下(鄰接表):

#include <bits/stdc++.h>
#define INF 0x3f3f3f3f  
#define MAXN 1010  
int n,m,ori;
 //點,邊,起點  
struct EDGE{int to,val,nxt;}e[MAXN];
//鄰接表 
int adj[MAXN],dis[MAXN],cnt=0,num[MAXN];//計數器 
bool vis[MAXN]={0};
//判斷是否在隊列 
std::queue < int > q;
void addedge(int u,int v,int w)	//鏈式前向星 
{
    e[++cnt].val=w; e[cnt].to=v; e[cnt].nxt=adj[u]; adj[u]=cnt;
}
bool SPFA(int ori)
{
	for(int i=1;i<=n;++i) dis[i]=(i==ori ? 0 : INF);
	q.push(ori); vis[ori]=1; ++num[ori];//起點入隊列時記得+1 
	while(!q.empty())
	{
		int u=q.front(); q.pop(); vis[u]=0;
		for(int i=adj[u];i;i=e[i].nxt) 
		{
			int v=e[i].to;
			if(dis[v]>dis[u]+e[i].val) 
			{
				dis[v]=dis[u]+e[i].val;
				if(!vis[v])
				{
					vis[v]=1;q.push(v);
					++num[v];//記錄加入次數 
					if(num[v]>n)	return 0;
					//若是這個點加入超過n次,說明存在負環,直接返回 
				}

			}
		}
	}
	return 1;
}
int main()
{
    std::scanf("%d%d%d",&n,&m,&ori); 
    for(int i=1;i<=m;++i)
    {
    	int u,v,w;	std::scanf("%d%d%d",&u,&v,&w);
	addedge(u,v,w);	
    }
    if(SPFA(ori));	std::printf("%d", dis[n]); 
    return 0;  
}

代碼風格其實和Dijkstra算法很像(代碼思想相似BFS)。惟一的區別就是SPFA中須要有計算器來判斷是否存在負環。

對於隨機數據而言,時間複雜度:O(kE)(k爲一個較小系數)

但SPFA能夠人爲的造數據,卡負環,致使其性能變得很是低。(時間複雜度達到指數級)

因此,若是題目中不存在負權邊,用Dijkstra算法最爲保險。

作題感悟:

  • 能夠運用在最長路中。(將一開始全部邊的權值都改成負數,這樣就所求出來的就是最長路)
  • SPFA,Bellman-Ford均可以用來判斷圖中是否存在負環,屬於判斷負環的模板。
相關文章
相關標籤/搜索