Dijkstra算法修改 + dfs算法的总结

本文深入探讨了Dijkstra算法与深度优先搜索(DFS)的结合应用,旨在解决单源点最短路径问题,包括边权之和、点权之和及路径节点打印。文章修正了常见错误,即在Dijkstra算法初始化阶段跳过起始节点,导致DFS无法正确回溯的问题。通过实例演示了如何在Dijkstra算法中更新最短路径节点及其前驱,以及如何利用DFS遍历这些路径。

Dijkstra + dfs

参考Dijkstra算法的改进

前提:Dijkstra算法的思路和实现请看最短路径算法之二:Dijkstra总结

介绍

Dijkstra算法和dfs算法的结合主要解决最短路径的边权之和、点权之和以及打印最短路径结点等问题。
通过Dijkstra算法找出单源点最短路径,在通过dfs 算法遍历最短路径中的结点。主要实现的方法是在Dijkstra算法每次更新dis数组的时候对可变大小的数组(vector)进行插入清除等操作,将每次更新的最短路径结点的前驱结点加入到 vector 中。最后在 dfs 算法中通过不断的递归获取前驱结点并加到另一个最短路径数组中(vector path)。最后打印path 就可以得到最短路径经过的结点(具体实现方法请看下文问题实例)。

我之前遇到的问题

我原来的思路是在Dijkstra算法中先初始化 dis 数组(存放开始结点到各结点的最短距离)。然后在通过遍历查找dis中最小的点并标记更新dis数组和pre(存放各结点的前驱结点),最后导致在dfs算法中出现问题(dfs不会有到边界的情况)。原因是我在Dijkstra算法中优先对dis数组进行了初始化,然后在遍历。这相当于我跳过了第一次从开始结点到目标结点时没有将开始结点当做前驱结点加入到 pre 中,所以dfs 不会有 (end == begin)。

修改(已对上篇博客进行修改)

将原来对dis初始化删除,只需要对 dis [ begin ] = 0。

Dijkstra算法的变化

红框内为主要代码

dfs算法核心代码

 //将当前结点的加入到临时路径中
	temppath.push_back(D); 
	//遍历结点 D 的前驱结点 
	for(int i = 0; i < pre[D].size(); i++)
	{
		dfs(pre[D][i]); 
	 } 
	//能执行到这说明这条最短路径已经遍历结束
	//回溯 
	temppath.pop_back();

具体案例

旅行者地图给出了高速公路沿线城市之间的距离,以及每条高速公路的成本。
现在你应该编写一个程序来帮助旅行者决定从他/她出发的城市到目的地的最短路径。
如果这样的最短路径不是唯一的,你应该输出最小代价的路径,它保证是唯一的。

输入规格:
每个输入文件包含一个测试用例。
每一种情况都以一行包含4个正整数N、M、S和D开始,
其中N(≤500)是城市的数量(因此城市编号从0到N-1);
M是高速公路的数量;S和D分别是出发城市和目的地城市。
然后是M行,每一行提供一条公路的信息,格式如下:
城市,城市,距离,花费
输出规范:
对于每个测试用例,在一行中打印从起点到目的地的最短路径上的城市,
然后是路径的总距离和总成本。数字之间必须用空格隔开,并且在输出结束时必须没有多余的空格。

Sample Input:
4 5 0 3
0 1 1 20
1 3 2 30
0 3 4 10
0 2 2 20
2 3 1 20
Sample Output:
0 2 3 3 40

代码

//最短距离是指 从开始城市到目标城市的最短距离
//最短路径是指 最短距离一样的不同路径 
#include <iostream>
#include <algorithm> 
#include <vector>
using namespace std;
const int inf = 888888;
int N,M,S,D;				//城市,关系,开始,结束
int ans1 = inf;				//花费之和  
int map[100][100]; 			//图
int sum[100][100];			//花费 
int dis[100];				//每次城市间的最短距离 
int vis[100]; 				//已经确认的城市间最短距离
vector<int> pre[100];		//每个城市最短路径的前驱结点
vector<int> path;		//最短路径
vector<int> temppath;	//临时最短路径 
//ps: pre[100] 可看作一个二维数组
//	   path、temppath 可看作一个一维数组 


//误区:原来思路:dijkstra算法首先初始化 dis从开始结点到各结点的距离,
//从而导致在遍历更新 dis 时没有将开始结点加入到前驱 pre 中(因为将前驱结点是从第二趟开始的,第一次被跳过了), 
//最终导致在调用dfs时出问题(dfs不会有边界的情况) 
//更改:不用初始化最开始dis到各点的距离, 只需要初始化 开始结点到开始结点为 dis[S] = 0; 
//		
//Dijkstra算法
void dijkstra(int S)
{
	dis[S] = 0;			//关键 
	//循环遍历 
	for(int i = 0; i < N; i++)
	{
		int temp = -1;		//标记找到的最小值 
		int aaa = inf;	//进行迭代松弛 
		for(int j = 0; j < N; j++)
		{
			if( !vis[j] && dis[j]< aaa)
			{
				aaa = dis[j];
				temp = j;
			}
		 } 
		if(temp == -1) return;
		vis[temp] = 1;
		//根据找到的最短路径更新 dis
		for(int j = 0; j < N; j++)
		{
			if(!vis[j])//找到没有确定最短路径的结点 
			{
				//判断是否是最短路径 
				if(dis[j] > dis[temp] + map[temp][j])
				{
					//更新dis 
					dis[j] = dis[temp] + map[temp][j];
					//清空 pre 之前存的数据 ,重新将最短路径的前驱结点加入 
					pre[j].clear();
					pre[j].push_back(temp);
				//如果经过 temp 前驱结点后的最短距离一样 
				}else if(dis[j] == (dis[temp] + map[temp][j])){
					//存在多条最短距离一样,但最短路径不一样,将其加入 pre 
					pre[j].push_back(temp);
				}
			}
		 } 	
	 } 
 } 

					
//dfs算法 
/*
关键问题:如果存在多条最短距离相同但最短路径不同的路线对其进行判断哪条路径花费最少(点权之和) 
核心:将各结点的前驱结点加入临时路径中,实现获得一条完整最短距离的最短路径的各结点 
*/
void dfs(int D)
{
	//出口边界 
	if(D == S)//如果 开始城市等于结束城市 退出 
	{
		//主要内容,对已经获得的最短路径 temppath进行判断是否是花费最少的 
		//将最后一个城市加入 temppath
		temppath.push_back(D); 
		int num = 0,id1,id2;	//num 为 这条最短路径的花费之和 
		//i 从 开始城市开始遍历。i 不能等于 1 是因为会造成越界 
		for(int i = temppath.size()-1; i > 0 ; i--)
		{
			id1 = temppath[i];		//前面城市 
			id2 = temppath[i-1];	//后面城市 
			num += sum[id1][id2];	//对经过的城市花费进行累加 
		}

		//如果这条边比之前的最短路径花费还少 
		 if(num < ans1)
		 {
		 	//更新最终的最短路径 
		 	ans1 = num; 
		 	path = temppath;
		 }
		//回溯
		temppath.pop_back();
		return; 
	 } 
	 
	 //将当前结点的加入到临时路径中
	temppath.push_back(D); 
	//遍历结点 D 的前驱结点 
	for(int i = 0; i < pre[D].size(); i++)
	{
		dfs(pre[D][i]); 
	 } 
	//能执行到这说明这条最短路径已经遍历结束
	//回溯 
	temppath.pop_back();
}


int main(int argc, char** argv) 
{
	while(cin >> N >> M >> S >> D)
	{
		//初始化
		for(int i = 0; i < N; i++)
		{
			for(int j = 0; j < N; j++)
			{
				if(i==j)
				{
					map[j][i] = 0;
				} else {
					map[i][j] = map [j][i] = inf;
					sum[i][j] = sum[j][i] = inf;
				} 
			}
		}
		fill(dis,dis+N,inf);
		fill(vis,vis+N,0);
		//输入 
		for(int i = 0; i < M; i++)
		{
			int a,b,c,d; 
			cin >> a >> b >> c >> d;
			map[a][b] = map[b][a] = c;	//各边关系 
			sum[a][b] = sum[b][a] = d;	//各边花费 
		}
		dijkstra(S);
		dfs(D);
		
		//经过Dijkstra 和 dfs 之后获得花费最少的最短路径 path 
		//输出结果 :经过的城市,总距离(开始城市到目标城市的距离),花费之和(dfs求出) 
		for(int i = path.size()-1; i >= 0; i--)
		{
			cout << path[i] << "   ";
		 } 
		cout << dis[D] << "   " << ans1 << endl;
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值