考研複試系列——第七節 最短路徑

考研複試系列——第七節 最短路徑

前言


前面我們學習了DFS算法,利用DFS算法,我們以每一個頂點爲開始節點進行DFS,最後進行比較也可以求得最短路徑,
但是複雜度不能滿足我們的需求。現在我們通過Floyd算法和Dijkstra算法來解決最短路徑問題。

Floyd算法


Floyd算法的思想很簡單,就是藉助第三個點來優化另外兩個頂點的距離。比如我們有三個頂點A,B,C。三個頂點相互連接,我們要求A到C的
最短路徑,肯定要比較A->B 和 A->C->B這兩條路徑的長度,後者即藉助了第三個頂點C。我們現在用二維數組 e[ i ][ j ] 表示一個有向圖,假如只
允許經過節點 1 來進行優化路徑,我們只需要遍歷節點,判斷原有距離與經過節點 1 的距離的大小,然後若比原有的小則進行更新,代碼如下:

//經過1號節點
for(i=1;i<=n;i++)//遍歷二維數組
{
	for(j=1;j<=n;j++)
	{
		if(e[i][j] > e[i][1] + e[1][j])//判斷經過節點1是否距離更短
			e[i][j] = e[i][1] + e[1][j];//更新二維數組(更新距離)
	}
}
//經過2號節點
for(i=1;i<=n;i++)//遍歷二維數組
{
	for(j=1;j<=n;j++)
	{
		if(e[i][j] > e[i][2] + e[2][j])//判斷經過節點2是否距離更短
			e[i][j] = e[i][2] + e[2][j];//更新二維數組(更新距離)
	}
}
//由上面的情況得到Floyd算法的核心代碼
for(k=1;k<=n;k++)//最外層循環,表示經常哪個中間節點
{
	for(i=1;i<=n;i++)//遍歷二維數組
	{
		for(j=1;j<=n;j++)
		{
			if(e[i][j] > e[i][k] + e[k][j])//判斷經過節點k是否距離更短
				e[i][j] = e[i][k] + e[k][j];//更新二維數組(更新距離)
		}
	}
}
看起來很簡單吧,但是要注意Floyd算法不能解決帶有負權值環路的問題,假如存在負權值環路,那麼每次繞一圈都減少的話,就無法找到最短路徑了。

下面來一道例題:
第一行輸入N,M (N<=100 , M <= 10000)其中N表示 N 個城市,M表示這些城市之間有M條路,接下來輸入M行,每行三個整數A,B,C
(1<= A, B <= N ,  1<= C <= 1000)表示城市A到B所需的距離。求標號爲1的城市到標號爲N的城市的最短距離。輸入N,M爲0 0 時結束。

思考:這道題目是一道典型的最短路徑問題,且不含負權值(環路),我們可以使用Floyd算法或者迪傑斯特拉算法解決。現在先使用Floyd算法。

#include<iostream>
using namespace std;

int ans[101][101];//圖的鄰接矩陣

int main()
{
	int N,M;
	while(cin>>N>>M && N && M)
	{
		int i,j;
		for(i=1;i<=N;i++)//鄰接矩陣初始化
		{
			for(j=1;j<=N;j++)
			{
				ans[i][j] = 999999;//表示不可達
			}
			ans[i][i] = 0;//城市到自己的距離爲0
		}
		int a,b,c;
		for(i=1;i<=M;i++)//輸入城市間距離
		{	
			cin>>a>>b>>c;
			ans[a][b] = ans[b][a] = c;//注意本題是無向圖
		}
		int k;
		for(k=1;k<=N;k++)//Floyd核心算法
			for(i=1;i<=N;i++)
				for(j=1;j<=N;j++)
					if(ans[i][j] > ans[i][k] + ans[k][j])
						ans[i][j] = ans[i][k] + ans[k][j];
		cout<<ans[1][N]<<endl;//輸出結果

	}
	return 0;
}

Dijkstra算法


//Dijkstra算法
/*

1 將所有頂點分爲兩個部分,已知最短路徑的頂點集合P和未知最短路徑的頂點集合Q。
  最開始,集合P中只有源節點一個。我們可以使用一個數組來表示對於某個頂點i,它
  是否存在於集合p中,數組[i]爲1表示在,爲0表示不在。
2 用dis數組來記錄源點到各頂點的距離,初始時設源點s到自己的距離爲0,即dis[s] = 0
  若存在源點能夠直接到達的頂點i,則設置dis[i] = e[s][i]。設置不可達的爲無窮
3 在集合Q的所有頂點中選擇一個離源點S最近的頂點u (即dis[u]最小)加入到集合P中,並考察
  所有以點u爲起點的邊,對每一條邊執行如下操作:
  如果存在一條從u到v的邊,判斷dis[u]+e[u][v]與dis[v]的大小,若前者小,則更新dis[v]爲
  dis[u]+e[u][v]。
4 重複執行第三步,如果集合Q爲空,算法結束。最終dis數組中的值就是源點到所有頂點的最短路徑

 */

我們使用Dijkstra算法來解決上面的問題

#include<iostream>
using namespace std;

int ans[101][101];//圖的鄰接矩陣
int book[101];//標記是否在集合P中
int dis[101];//保存距離

int main()
{
	int N,M;
	while(cin>>N>>M && N && M)
	{
		int i,j;
		for(i=1;i<=N;i++)//鄰接矩陣初始化
		{
			for(j=1;j<=N;j++)
			{
				ans[i][j] = 999999;//表示不可達
			}
			ans[i][i] = 0;//城市到自己的距離爲0
		}
		int a,b,c;
		for(i=1;i<=M;i++)//輸入城市間距離
		{	
			cin>>a>>b>>c;
			ans[a][b] = ans[b][a] = c;//注意本題是無向圖
		}

		for(i=1;i<=N;i++)//dis數組初始化
			dis[i] = ans[1][i];
		
		for(i=1;i<=N;i++)//初始化book數組
			book[i] = 0;
		book[1] = 1;//結點1爲源點
		for(i=1;i<=N-1;i++)//Dijkstra核心算法
		{
			//在集合Q中尋找距離源點最近的頂點u
			int min = 99999,u,v;
			for(j=1;j<=N;j++)
			{
				if(book[j] == 0 && dis[j] < min)
				{
					min = dis[j];
					u = j;
				}
			}
			book[u] = 1;//將頂點u加入集合P
			for(v=1;v<=N;v++)//判斷與更新dis數組
			{
				if(dis[v] > dis[u] + ans[u][v])
					dis[v] = dis[u] + ans[u][v];
			}
		}
		cout<<dis[N]<<endl;
	}
	return 0;
}

再來一道上述題目的變形:
給你n個點,m條無向邊,每條邊都有長度d和花費p ,給你起點s和終點t,要求輸出起點到終點的最短距離及其花費。如果
最短距離有多條路線,則輸出花費最少的。
輸入n,m,點的編號是1到n,然後是m行,每行4個數a,b,d,p,表示a和b之間有一條邊,且其長度爲d,花費爲p。
最後一行是兩個數s,t起點s終點t。n和m爲0時輸入結束。(1< n <= 1000 , 0< m < 100000 ,s != t)
sample input:
3 2
1 2 5 6
2 3 4 5
1 3 
0 0
sample output:
9 11

思考:這道題目和上一道大體一樣,只是多了花費,我們只要處理好花費就可以了,且我們可以看到花費和距離的性質是相同的,我們可以採取
相同的處理策略,只是在距離一致時,判斷花費的多少即可。詳情見代碼:

#include<iostream>
using namespace std;

int ans[1001][1001];//保存有向圖距離
int anm[1001][1001];//保存有向圖花費
int book[1001];//標記頂點是否在集合P中
int dis[1001];//保存距離
int money[1001];//保存花費

int main()
{
	int n,m;
	while(cin>>n>>m && n && m)
	{
		int a,b,d,p;
		int i,j;
		for(i=1;i<=1000;i++)//距離以及花費數組的初始化
		{
			for(j=1;j<=1000;j++)
				ans[i][j] = anm[i][j] = 99999;
			ans[i][i] = anm[i][i] = 0;
		}
		for(i=1;i<=m;i++)//輸入數據
		{
			cin>>a>>b>>d>>p;
			ans[a][b] = ans[b][a] = d;//注意是無向圖
			anm[a][b] = anm[b][a] = p;
		}
		int s,t;
		cin>>s>>t;//輸入起點和終點
		for(i=1;i<=n;i++)//dis和money以及book數組初始化
		{
			dis[i] = ans[s][i];
			money[i] = anm[s][i];
			book[i] = 0;
		}
		book[s] = 1;//將源點加入到集合P
		for(i=1;i<=n-1;i++)//選擇其餘n-1個節點依次加入到集合P直到集合P包含所有節點
		{
			int min = 999999,u,v;
			for(j=1;j<=n;j++)//從集合Q中尋找一個距離源點最小的節點
			{
				if(book[j] == 0 && dis[j] < min)
				{
					min = dis[j];
					u = j;
				}
			}
			book[u] = 1;//加入節點u
			for(v=1;v<=n;v++)//以u爲中轉,判斷源點到節點v的情況與更新
			{
				if(dis[v] > dis[u] + ans[u][v])//經過節點u距離變小了
				{
					dis[v] = dis[u] + ans[u][v];//更新距離
					money[v] = money[u] + anm[u][v];//更新花費
				}
				else if(dis[v] == dis[u] + ans[u][v])//如果最小距離相等
				{
					if(money[v] > money[u] + ans[u][v])//選擇花費更小的
						money[v] = money[u] + anm[u][v];
				}
			}
		}
		cout<<dis[t]<<" "<<money[t]<<endl;
	}
	return 0;
}
注意:如果對於空間有要求的話可以使用結構體來保存頂點和花費以及距離信息。

對於考研複試,掌握這些已經達到基本要求了,對於負權值邊如何處理,感興趣的可以去看下Bellman-Ford算法。


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