最短路径之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();
}

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