最短路徑之Dijkstra算法

注:本文總結自《算法筆記》

介紹

“迪傑斯特拉算法”,解決單源最短路問題,即給定圖G和起點s,計算s到達其他每個頂點的最短距離的問題。
基本思想:對圖G(V,E)設置集合S,存放已被訪問的頂點,然後每次從集合V-S(V減S)中選擇與起點s距離最小的一個頂點(記爲u),訪問並加入集合S,令頂點u爲中介點,優化起點s與所有從u能到達的頂點v之間的最短距離。這樣操作n次,直到集合S包含所有頂點。

具體實現

僞碼

Dijkstra(G,d[],s) {  //d表示從起點到各點的最短路徑的長度,s爲起點
	初始化;
	for(循環n次) {
		u=使d[u]最小的還未被訪問的頂點的標號;
		記u已被訪問;
		for(從u出發能到達的所有頂點v) {
			if(v未被訪問&&以u爲中介點使s到頂點v的最短距離更優) {
				優化d[v];
				令v的前驅爲u;
			}
		}
	}
}

鄰接矩陣實現

適用於點數不大(一般不超過1000)的情況。其中,鄰接矩陣G存放兩點間的距離。

const int MAXV = 1000;
const int INF = 1000000000;
int n, G[MAXV][MAXV], d[MAXV]; //d表示從起點到各點的最短路徑的長度
int pre[MAXV]; //pre[v]表示從起點到頂點v的最短路徑上v的前一個頂點
bool vis[MAXV] = { false };
void Dijkstra(int s) {
	fill(d, d + MAXV, INF);
	for(int i=0;i<n;i++) pre[i]=i;//初始化狀態設每個點的前驅爲自身
	d[s] = 0;
	for (int i = 0; i < n; i++) {  //循環n次 
		int u = -1, MIN = INF;
		for (int j = 0; j < n; j++) { //找到未訪問頂點中d[]最小的 
			if (vis[j] == false && d[j] < MIN) {
				u = j;
				MIN = d[j];
			}
		}
		if (u == -1) return; //剩下的頂點與起點s不連通
		vis[u] = true;
		for (int v = 0; v < n; v++) {
			if (vis[v] == false && G[u][v] != INF && d[u] + G[u][v] < d[v]) {
				d[v] = d[u] + G[u][v];
				pre[v]=u; //記錄v的前驅爲u
			}
		}
	}
}
//遞歸訪問最短路徑
void DFS(int s,int v){ //s爲起點編號,v爲當前訪問的頂點編號 
	if(v==s){
		printf("%d\n",s);
		return;
	} 
	DFS(s,pre[v]);
	printf("%d\n",v);
} 

加入其它條件

題目一般不會考得這麼“裸”,更多時候會加入其它條件。
對於新增條件,只需要增加一個數組存放新增的邊權或點權或最短路徑條數,且只需要修改Dijkstra函數中優化d[v]的那一個步驟。
這種新增條件一般作爲第二標尺

新增邊權

以新增的邊權代表花費爲例,cost[u][v]代表u->v的花費。

for(int v=0;v<n;v++){
	if(vis[v]==false&&G[u][v]!=INF){
		if(d[u]+G[u][v]<d[v]){
			d[v]=d[u]+G[u][v];
			c[v]=c[u]+cost[u][v]; //c[v]表示從起點s到v的最小花費 
		}else if(d[u]+G[u][v]==d[v]&&c[u]+cost[u][v]<c[v]){
			c[v]=c[u]+cosy[u][v];
		} 
	}
} 

新增點權

以新增點權代表城市中能收集到的物資爲例,weight[u]表示城市u中的物資數目。

for(int v=0;v<n;v++){
	if(vis[v]==false&&G[u][v]!=INF){
		if(d[u]+G[u][v]<d[v]){
			d[v]=d[u]+G[u][v];
			w[v]=w[u]+weight[v]; //w[v]表示從起點s到v收集到的最大物資數 
		}else if(d[u]+G[u][v]==d[v]&&w[u]+weight[v]>w[v]){
			w[v]=w[u]+weight[v];
		} 
	}
} 

求最短路徑條數

for(int v=0;v<n;v++){
	if(vis[v]==false&&G[u][v]!=INF){
		if(d[u]+G[u][v]<d[v]){
			d[v]=d[u]+G[u][v];
			num[v]=num[u]; //從起點s到v的最短路徑條數 
		}else if(d[u]+G[u][v]==d[v]){
			num[v]+=num[u]; //最短距離相同時累加 
		} 
	}
}

Dijkstra+DFS

對於優化條件比較複雜,比如需要在Dijkstra中根據路徑長度來優化,或者不滿足最優子結構。這時先用Dijkstra算法求出所有路徑最短的路徑,再DFS算法結合第二、三標尺判斷這些路徑中最優路徑(還可以統計路徑最短的路徑數量)。

最優子結構:即通過遞推可以將問題的規模縮小但不影響最終結果的結構,有點像貪心算法的感覺。

模板

注意:
(1)一般不管什麼問題Dijkstra代碼都可以不改動,只需要根據判斷最優的條件更改DFS函數即可。

(2)temppath中從第一個到最後一個存放的是從終點到起點。
(3)DFS傳入參數爲終點下標。

vector<int> pre[maxn]   //Dijkstra得到的所有路徑(每個點的前驅)
void Dijkstra() {
	fill(d, d + maxn, inf);
	d[0] = 0;
	for (int i = 0; i <= n; i++) {
		int u = -1, MIN = inf;
		for (int j = 0; j <= n; j++) {
			if (vis[j]==false&&d[j] < MIN) {
				u = j; MIN = d[j];
			}
		}
		if (u == -1) return;
		vis[u] = true;
		for (int v = 0; v <= n; v++) {
			if (vis[v] == false && G[u][v] != inf) {
				if (d[u] + G[u][v] < d[v]) {
					d[v] = d[u] + G[u][v];
					pre[v].clear(); //注意要clear
					pre[v].push_back(u);
				}
				else if (d[u] + G[u][v] == d[v]) {
					pre[v].push_back(u);
				}
			}
		}
	}
}


int optvalue, int num; //最優值,最短路徑的數量
vector<int> path, tempath; //最優路徑,臨時路徑
void dfs(int v) {
	tempath.push_back(v);
	//遞歸邊界
	if (v == s) {
	    num++;  //到達起點則數量+1
		int value;
		計算路徑tempath上的value值
		if (value優於optvalue) {
			optvalue = value;
			path = tempath;
		}
		tempath.pop_back();
		return;
	}
	//遞歸式
	for (int i = 0; i < pre[v].size(); i++) {
		dfs(pre[v][i]);
	}
	tempath.pop_back();
}

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