1. 程式人生 > >A.pro讀演算法の11:最短路徑之Dijkstra演算法

A.pro讀演算法の11:最短路徑之Dijkstra演算法

此文是獻給OIer看的。講的東西比較基礎(其實我理解Dijkstra花了很長時間)。NOIP2018結束約有1個月了,但是我們仍要繼續前進,為NOIP2019做準備。本節學習Dijkstra的演算法思想和實現,以及優先佇列和堆優化。線段樹也可以做到優化,甚至可能還更快,但是我太弱了不會。。

好了,本蒟蒻A.pro今天給大家帶來的是----A.pro讀演算法の11:最短路徑之Dijkstra演算法。

還有,我是真的菜。

目錄

1.1 引言

1.2 Dijktra的不歸路

1.3 能不能再給力一點呢?

  • 1.3.1 鄰接表
  • 1.3.2 堆優化
  • 1.3.3 Priority_Queue優化

1.1 引言

SPFA演算法被卡掉的機率很大,而Dijkstra是一個相對穩定的演算法。

Dijkstra演算法是指定一個點(原點)到其餘各點的最短路徑,所以叫做“單源最短路徑”如果最短路徑是什麼意思都不知道的話,來,我來幫你百度一下

演算法8中,我們已經初步瞭解了圖是個什麼東東。圖這個東西大概長這樣:

一個圓就是一個頂點,圓旁邊的字母代表序號,溝通兩點之間的線叫做邊。邊加上了箭頭,代表這個邊是有方向的。所以這張圖是有向圖

1.2 Dijkstra的不歸路

見標滾。(光速逃)

在Dijkstra演算法中,有兩種點。(一種是我看順眼的點,一種是我看不順眼的點!)

這兩種點,一種是已經確定最短路徑的點(又稱為紅點),一種是尚未確定最短路徑的點(又稱為藍點)。紅點的最短路徑不會再改變。

換言之,如果點i是紅點,則dis_{i}將不再改變,反之亦然。

然後,還有一個數組dis。這個陣列代表了原點到其餘各個頂點的初始最短路徑。例如A點是原點,如下圖(A=1,B=2,C=3,D=4,E=5,F=6)。

我們以A為原點,來求所有點到A點的最短路徑。

對了!我們剛剛已經建立好dis陣列了,dis裡面的值被稱為最短路徑的“估計值”,dis[i]表示第i號點到源點(A點)的咕計值。什麼是咕計值呢?因為這個值會不斷更新(減小),更新到一定次數就會變成“確定值”

了,即原點到當前點的最短路徑。這個我們一會再說。

先建立一個鄰接矩陣a,a[i][j]=v表示從i到j這條邊的權值(長度)是v。

最開始,dis初始值除了源點本身都是無窮大。源點本身(從自己到自己)都是0。上圖中dis[2]和dis[3]並不是一開始就是有值的,而是為了讓讀者直觀瞭解什麼是dis陣列而賦值。

dis初始化:

既然是求A點到其餘各點的最短路徑,那麼先找一個離A點近的頂點。通過鄰接矩陣中可知,當前離A點最近的是B點。B點,a[1][2]=1,A點到B點的距離是1,比無窮大要小,所以dis[2]從max變成了1。順便,我們開一個變數minx,記錄離A點最近的點,留著以後會用。

然後搜到C點,map[1][3]=12,距離是12,比原來的dis[3]的∞小,於是dis[3]=12。但是12比dis[2]的1要大,所以minx不更新。

minx=2,也就是說從A到B比從A到C要近。那我們選擇B點(也就是把B當成原點),求所有點到它的最短路徑,加上它到真正的源點(A點)的距離,就是我們要求的最短路。注意,當我們選擇B點進行拓展時,dis[2]的值就已經從咕計值變成了確定值。也就是說,從A點到B點的最短路徑就是dis[2]值。

從B點開始,有哪些出邊呢?通過上圖可知有B->C和B->D兩條路。

從B點開始,搜到C點,a[2][3]=9,原本的dis[3]=12,發現dis[2]+map[2][3]=1+9=10 < dis[3](12)。我們把dis[3]改為10.這個過程我們叫做鬆弛,A點到C點的路程即dis[3](12),通過B點到C點使這條變鬆弛成功。

我們看minx的值會不會動。minx是記錄離A點最近的點,現在是記錄離B點最近的點。是C點。於是更新為minx=3。

與B點相連的還有D點。a[2][4]=3,原本dis[4]=max,所以可以將dis[4]的值鬆弛為4(dis[2]+a[2][4]=1+3=4,dis[4]>dis[2]+a[2][4],所以dis[4]鬆弛為4)。

a[2][4]=3,a[2][4]<a[2][3],3<9,minx更新為4。

與B相連的點都搜完了,最後發現minx=4,那我們從D點繼續搜。此時,dis[4]的值已經從咕計值變成了確定值。

接下來對D點的所有出邊(D->C,D->E,D->F)用剛才的方法進行鬆弛。

從D點開始,搜到C點,a[4][3]=3,原本dis[3]=10,a[4][3]+dis[4]=3+4=7,7<dis[3],將dis[3]鬆弛為7。minx=3。

接著搜E點,a[4][5]=12,原本dis[5]=max,a[4][5]+dis[4]=12+4=16,16<dis[5],將dis[5]鬆弛為16。a[4][5]=12,a[4][3]=3,12>3,minx不變。minx=3。

接著搜F點,a[4][6]=14,原本dis[6]=max,a[4][6]+dis[4]=14+4=18,18<dis[6],將dis[6]鬆弛為18。a[4][6]=14,a[4][3]=3,14>3,minx不變。minx=3。

與D相連的點都搜完了,最後發現minx=3,那我們從C點繼續搜。此時,dis[3]的值已經從咕計值變成了確定值。

從C點開始搜,發現只能到達E點。a[3][5]=5,原本dis[5]=16,a[3][5]+dis[5]=7+5=12,12<dis[5],將dis[5]鬆弛為12。minx=5。

與C相連的點都搜完了,最後發現minx=5,那我們從E點繼續搜。此時,dis[5]的值已經從咕計值變成了確定值。

與E相連的有F點。a[5][6]=3,原本dis[6]=18,a[5][6]+dis[5]=12+3=15,15<dis[6],將dis[6]鬆弛為15。minx=6。

此時dis[6]的值也變成了確定值。我們發現F點已經沒有出邊了,於是不用處理。到此,dis陣列中所有的值都已經從咕計值變成了確定值。(完結撒花)

最終dis陣列如下,這便是A點到其餘各個頂點的最短路徑。(蒟蒻A.pro表示很神奇)

OK,回顧一下剛才的演算法。演算法的基本思想是:每次找到離原點(上面例子是A點)最近的一個頂點,然後以此頂點為中心進行擴充套件,最後得到原點到其餘所有點的最短路徑

好的,基本程式碼如下:

#include <stdio.h>
#include <iostream>
#include <string.h>
#define inf 1<<20
using namespace std;;
int a[101][101],dis[101],b[101],n,m,minx;
//a陣列儲存圖的資訊,dis儲存原點到其餘各點的初始路程
//b陣列記錄哪些點是已知最短路徑頂點,哪些點是未知最短路徑頂點
//minx記錄與當前點最近的是哪個點
inline void Dijkstra()
{
	register int i,j,k;
	for(i=1;i<=n-1;i++)//查詢最近點。除了最後一個頂點,其他點都要被搜一次
	{
		k=inf;
		for(j=1;j<=n;j++)//尋找與當前點相連的點 
		{
			if(b[j]==0 && dis[j]<k)//如果是未知最短路徑的點,並且當前點最短路徑小於k 
			{
				k=dis[j];//不斷更新當前點的最短路徑 
				minx=j;//找到離源點最近的點,然後把編號記錄下來,用於搜尋。
			}
		}
		b[minx]=1;//離原點最近的點從咕計值變為確定值 
		for(j=1;j<=n;j++)//對當前點的出邊點進行鬆弛操作 
		{
			dis[j]=min(dis[j],dis[minx]+a[minx][j]);
		}
	}
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	register int i,j;
	cin>>n>>m;//輸入n和m,n是頂點個數,m是邊的個數 
	for(i=1;i<=n;i++)//鄰接矩陣初始化 
	{
		for(j=1;j<=n;j++)
		{
			if(i==j)//自己到自己的最短距離是0 
			{
				a[i][j]=0;
			}
			else//假設每個點都不相連 
			{
				a[i][j]=inf;
			}
		}
	}
	
	for(i=1;i<=m;i++)//讀入邊的資訊 
	{
		int t1,t2,t3;
		cin>>t1>>t2>>t3;
		a[t1][t2]=t3;
	}
	for(i=1;i<=n;i++)//初始化dis陣列,這是原點(假設原點是1)到其他各個頂點的初始路程 
	{
		dis[i]=a[1][i];
	}
	memset(b,0,sizeof b);//b陣列初始化
	b[1]=1;//標記原點是已知最短路徑的頂點,用1表示已知最短路徑的頂點,用0表示未知最短路徑的頂點
	//因為dis陣列中的值是最短路的“估計值”,也許這些值要經過改變(鬆弛)。
	Dijkstra();
	for(i=1;i<=n;i++)//輸出結果 
	{
		cout<<dis[i]<<' ';
	}
	return 0;
}

輸入:

6 9
1 2 1
1 3 12
2 3 9
2 4 3
3 5 5
4 3 3
4 5 12
4 6 14
5 6 3

輸出:

0 1 7 4 12 15

不乖的小盆友:那萬一是負權邊呢?

蒟蒻A.pro:哪條路的路長會為負......

負權邊這個東西,也不是不存在的。怎麼解決呢?

答案就是:

無法解決。

鼓掌。(逃)

Dijkstra演算法只能解決正權圖上的最短路問題問題。