三種最短路模板

一、Floyd算法——只有五行代碼的算法

算法思想:

從節點i到節點j的最短路徑只有2種可能:

1)直接從節點i到節點j

2)從節點i經過若干個節點k到節點j

假設dis(i,j)爲節點i到節點j的最短路徑的距離

對於每一個節點k,我們檢查dis(i,k)+dis(k,j) < dis(i,j)是否成立,

如果成立,證明從i到k再到j的路徑比i直接到j的路徑短.

於是dis(i,j) = dis(i,k) + dis(k,j),這樣一來,當我們遍歷完所有k,dis(i,j)中記錄的便是節點i到節點j的最短路徑的距離

缺點:時間複雜度太高 O(n^3)

模板:

void Floyd()
{
	for(int k = 1; k <= N; k++)  
        for(int i = 1; i <= N; i++) 
            for(int j = 1; j < N; j++) 
                if(dis[i][k] + dis[k][j] < dis[i][j]) 
                    dis[i][j] = dis[i][k] + dis[k][j];
}
        

二、Dijkstra算法

如下圖:

我們還需要用一個一維數組 dis 來存儲 1 號頂點到其餘各個頂點的初始路程,如下:

 

既然是求 1 號頂點到其餘各個頂點的最短路程,那就先找一個離 1 號頂點最近的頂點。

通過數組 dis 可知當前離 1 號頂點最近是 2 號頂點。當選擇了 2 號頂點後,即 1 號頂點到 2 號頂點的最短路程就是dis[2]的值

既然選了 2 號頂點,接下來看 2 號頂點有 2-3 和 2-4 這兩條邊。

先思考通過 2->3 這條邊能否讓 1 號頂點到 3 號頂點的路程變短。也就是說現在來比較 dis[3]和 dis[2]+e[2][3]的大小。其中 dis[3]表示 1 號頂點到 3 號頂點的路程,dis[2]+e[2][3]中 dis[2]表示 1 號頂點到 2 號頂點的路程,e[2][3]表示 2-3 這條邊。所以 dis[2]+e[2][3]就表示從 1 號頂點先到 2 號頂點,再通過 2->3 這條邊,到達 3 號頂點的路程。

我們發現 dis[3]=12,dis[2]+e[2][3]=1+9=10,dis[3]>dis[2]+e[2][3],因此 dis[3]要更新爲 10,以此類推。

基本思想:每次找到離源點最近的一個頂點,然後以該頂點爲中心進行擴展,最終得到源點到其餘所有點的最短路徑。

時間複雜度:O(n^2)

代碼:

1.初始化

void mem()
{
	for(int i=1;i<=n;i++)
    for(int j=1;j<=n;j++)
    {
        if(i==j)e[i][j]=0;
        else e[i][j]=INF;
    }
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&v1,&v2,&w);
        e[v1][v2]=w;
    }
    for(int i=1;i<=n;i++)dis[i]=e[1][i];
    for(int i=1;i<=n;i++)vis[i]=0;
    vis[1]=1; 
}

2.算法核心

void Dijkstra()
{
    for(int i=1;i<=n-1;i++)
    { 
        int min_num=INF;
        int min_index=0;
        for(int k=1;k<=n;k++) 
            if(min_num>dis[k] && book[k]==0)
            {
                min_num=dis[k];
                min_index=k;
            }
        book[min_index]=1; 
        for(int j=1;j<=n;j++)
            if(e[min_index][j]<INF)
                if(dis[j]>dis[min_index]+e[min_index][j])
                    dis[j]=dis[min_index]+e[min_index][j];
    }
}

三、SPFA算法

算法優點:

 1.時間複雜度比普通的Dijkstra和Ford

 2.能夠計算負權圖問題。 

實現方法:

1.存入圖。可以使用鏈式前向星或vector

2.開一個隊列,先將開始的節點入隊。

3.每次從隊列中取出一個節點X,遍歷與X相通的Y節點,比對  b的長度 和 a的長度+ a與b的長度

 如果a的長度+ a與b的長度 b的長度——需要更新。

1)存入最短路。

2)由於改變了原有的長度,所以需要往後更新,與這個節點相連的最短路。(即:判斷下是否在隊列,在就不用重複,不在就加入隊列,等待更新)。

3)在這期間可以記錄這個節點的進隊次數,判斷是否存在負環。

 4.直到隊空。

判斷有無負環:如果某個點進入隊列的次數超過N次則存在負環

代碼:

void SPFA()//核心代碼
{
	queue <int> q;
	memset(vis,inf,sizeof(vis));
	memset(ven,0,sizeof(ven));
	memset(nums,0,sizeof(nums));
	vis[s]=0;
	ven[s]=1;
	q.push(s);
	while(!q.empty())
	{
		int x=q.front();
		q.pop(); 
		ven[x]=0; 
		for(int i=pre[x]; ~i; i=a[i].next)
		{
			int y=a[i].y;
			if(vis[y]>vis[x]+a[i].time)
			{
				vis[y]=vis[x]+a[i].time;
				if(!ven[y])
				{
					q.push(y);
					ven[y]=1; 
				}
			}
		}
	}
}

存圖代碼:(鏈式前向星)

void add(int x,int y,int k)//鏈式前向星,加入節點
{
	a[cent].y=y;
    a[cent].time=k;
    a[cent].next=pre[x];
	pre[x]=cent++;
}

參考資料:

《啊哈,算法》

http://keyblog.cn/article-21.html

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章