1003. Emergency<Dijstra><多条最短路径>

本文提供了一种使用Dijkstra算法解决PAT A 1003问题的具体实现方法,通过遍历所有节点来寻找最短路径,并记录路径数量与最大团队数。代码详细展示了初始化、寻找最近节点及更新距离的过程。

题目链接:http://pat.zju.edu.cn/contests/pat-a-practise/1003

参考他人代码。


这种Dijstra的实现,与之前的文章提到的Dijstra的实现不同(http://blog.youkuaiyun.com/staibin/article/details/20702925)。之前提到的是直接初始化起点,遍历n-1个节点。现在这种实现是遍历n个节点,所以过程上有些差异。

#include <stdio.h>

#define SIZE	500+5
#define INF		1<<30

int n, m, s, e;
int map[SIZE][SIZE];
bool mark[SIZE];
int dis[SIZE];	// 必备

int team[SIZE];
int maxteam[SIZE];
int pathcount[SIZE];// 题目需求


void dij()
{
	// 1. 寻找最近点
	// 2. 未找到或为终点,返回
	// 3. 把改点加入,处理与改点相连的点

	// 初始化
	pathcount[s] = 1;
	dis[s] = 0;
	maxteam[s] = team[s];

	int i, j;
	for(i=0; i<n; i++)
	{
		int p, min = INF;
		for(j=0; j<n; j++)
		{
			if(mark[j] == false && dis[j] < min)
			{
				p=j;
				min=dis[j];
			}
		}// 寻找未被访问且距离最小的节点

		if(p == e || min == INF)
		{
			return ;
		}// 到终点,或者没有所连的点

		mark[p] = true;
		for(j=0; j<n; j++)
		{
			if(mark[j] == false)
			{
				int discost = dis[p]+map[p][j];// 计算未访问节点距起点的距离
				if(discost < dis[j])
				{
					dis[j] = discost;
					pathcount[j] = pathcount[p];
					maxteam[j] = maxteam[p]+team[j];
				}
				else if(discost == dis[j])//以前有点可到达节点i
				{
					// pathcount[j]++;
					// 这样是错的,因为到p的路径可能不止一条
					pathcount[j] += pathcount[p];
					maxteam[j] = maxteam[j] > maxteam[p]+team[j] ? maxteam[j] : maxteam[p]+team[j];
				}// (a) new path
				else
				{
					// a longer path, doing nothing
				}
			}
		}
	}// 遍历n个节点
	return ;
}

void Init()
{
	int i;
	for(i=0; i<n; i++)
	{
		mark[i] = false;
		dis[i] = INF;
		team[i] = 0;
		pathcount[i] = 0;
		maxteam[i] = 0;
		for(int j=0; j<n; j++)
		{
			map[i][j] = INF;
		}
	}
	return ;
}

int main()
{
	#ifdef ONLINE_JUDGE
	#else
		freopen("E:\\in.txt", "r", stdin);
	#endif

	scanf("%d%d%d%d", &n, &m, &s, &e);
	Init();
	int i;
	for(i=0; i<n; i++)
	{
		scanf("%d", &team[i]);
	}

	for(i=0; i<m; i++)
	{
		int c1, c2, l;
		scanf("%d%d%d", &c1, &c2, &l);
		map[c1][c2] = map[c2][c1] = l;
	}

	dij();
	
	printf("%d %d\n", pathcount[e], maxteam[e]);
	return 0;
}


<think>我们正在讨论Dijkstra算法能否找出多条最短路径的问题。根据用户提供的引用材料,特别是引用[3]中详细描述了Dijkstra算法的过程:它每次从Q集合中找出一个距离起点代价最小的结点u,将其加入S集合(已确定最短路径的集合),然后对u的相邻结点进行松弛操作。注意,算法每次只选择一个最小路径的结点,并且一旦确定某个结点的最短路径后,该结点的最短路径长度就不会再被更新(因为后续的松弛操作只针对那些尚未确定最短路径的结点)。因此,标准的Dijkstra算法只能找到一条最短路径(即最先被确定的那条路径),即使存在多条长度相同的最短路径,它也不会记录所有路径。 但是,我们可以对Dijkstra算法进行修改,使其能够记录多条最短路径。通常的做法是: 1. 在松弛操作时,如果发现一条与当前记录的最短路径长度相同的路径,则将其加入到路径集合中。 2. 使用一个数组(或列表)来存储到达每个结点的所有最短路径(如果有多条的话)。 具体修改如下: - 在初始化时,除了距离数组d,我们还需要一个数组(例如vector<vector<int>> paths[maxn])来存储到达每个结点的所有最短路径(路径可以用结点序列表示,或者存储前驱结点列表)。 - 在松弛操作时,当我们发现一条新的路径长度等于当前记录的最短路径长度(即d[v])时,我们不是覆盖,而是将这条新路径添加到v的路径集合中(或者将当前结点u添加到v的前驱结点列表中)。 - 如果发现一条更短的路径,则更新d[v],并清空v之前存储的路径(因为之前存储的路径都比新路径长),然后加入这条新路径。 然而,需要注意的是,Dijkstra算法本身是单源最短路径算法,它主要关注最短路径的长度,而不是路径的条数。标准实现中,我们只记录一条最短路径(通过记录前驱结点)。如果要记录所有最短路径,我们需要在松弛操作中处理等长路径的情况。 修改后的松弛操作伪代码: if d[u] + w(u, v) < d[v]: d[v] = d[u] + w(u, v) prev[v] = [u] # 清空前驱列表,只保留当前这个前驱 else if d[u] + w(u, v) == d[v]: prev[v].append(u) # 添加一个前驱结点,表示从u也可以得到一条同样长度的最短路径 这样,对于每个结点v,prev[v]存储了所有可能的前驱结点(这些前驱结点都在一条最短路径上)。然后,我们可以通过回溯(从终点开始,递归地遍历所有前驱结点)来重建所有最短路径。 但是,需要注意的是,这样修改后,算法的时间复杂度和空间复杂度会增加,因为需要存储和回溯多条路径。 总结: 1. 标准Dijkstra算法只能找到一条最短路径(最先找到的那条)。 2. 通过修改算法,在松弛操作中记录所有可能的前驱结点(当遇到等长路径时),可以找到所有长度相同的最短路径。 因此,回答用户的问题:Dijkstra算法本身设计用于找到一条最短路径,但经过适当修改,可以找出所有最短路径(如果存在多条长度相同的最短路径)。 下面,我们根据用户提供的引用[4]中的代码模板,展示如何修改以记录多条最短路径。注意,我们只修改松弛部分,并增加前驱列表。 修改后的代码(部分): - 将vis数组(记录是否已确定最短路径)和d数组(记录最短距离)保持不变。 - 增加一个前驱数组:vector<int> pre[maxn]; // 每个结点可能有多个前驱结点 在松弛操作中: 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(); // 清空之前的前驱 pre[v].push_back(u); // 加入新的前驱 }else if(d[u] + G[u][v] == d[v]){ pre[v].push_back(u); // 添加一个等长的前驱 } } } 注意:在每次确定一个结点u的最短路径后(即将其加入S集合),我们才进行松弛操作。在松弛操作中,我们更新相邻结点的距离,并记录前驱。 但是,由于在Dijkstra算法中,我们每次只处理一个最小距离的结点,并且一旦结点被加入S集合,它的最短路径就不会再改变。因此,当我们发现一条新的路径长度等于当前记录的最短路径长度时,说明存在另一条同样长的路径。 最后,通过前驱数组,我们可以用深度优先搜索(DFS)从终点回溯到起点,得到所有最短路径。 然而,需要注意的是,在存在多条等长最短路径的情况下,我们记录所有前驱,因此回溯时可能会生成指数级的路径数量(如果图的结构特殊,比如网格图)。所以,当需要输出所有路径时,可能会非常耗时。 因此,如果问题只要求输出最短路径的条数,而不需要具体路径,我们可以用一个数组num[]来记录到达每个结点的最短路径条数,在松弛操作时更新: if d[u] + G[u][v] < d[v]: d[v] = d[u] + G[u][v]; num[v] = num[u]; // 重新计数 else if d[u] + G[u][v] == d[v]: num[v] += num[u]; // 加上当前这条路径的条数 注意:初始时,起点的路径条数num[s]=1。 综上所述,Dijkstra算法可以修改为寻找多条最短路径,但标准实现只提供一条。 下面我们根据用户的问题,给出一个完整的回答。</think>### Dijkstra算法能否找出多条最短路径? **标准Dijkstra算法只能找到一条最短路径**(最先被确定的最短路径),但通过适当修改,可以使其识别多条等长最短路径。以下是详细分析: #### 一、标准算法的局限性 1. **贪心策略特性** 算法每次从集合$Q$(未确定最短路径的节点)中**选择距离起点最近的节点$u$加入集合$S$**(已确定路径的节点),并固定其路径长度[^3]。 - 当存在多条等长最短路径时,算法只会选择**最先达到最小距离值的那条路径** - 后续路径即使长度相同,也会因$u$已被移入$S$集合而不再更新[^3] 2. **路径存储机制** 标准实现仅记录每个节点的**唯一前驱节点**(通过`prev[v]`或松弛操作更新),无法回溯多条路径[^4]。 --- #### 二、如何修改算法以获取多条路径 通过以下修改可支持多条最短路径的识别: ```mermaid graph LR A[标准Dijkstra] --> B[增加前驱节点列表] B --> C[修改松弛操作] C --> D[回溯所有路径] ``` 1. **修改数据结构** 将单一前驱节点改为**前驱节点列表**: ```cpp vector<int> pre[maxn]; // 存储所有可能的前驱节点 ``` 2. **调整松弛操作** 在松弛阶段增加等长路径的判断: ```cpp if (d[u] + G[u][v] < d[v]) { d[v] = d[u] + G[u][v]; pre[v].clear(); // 清空旧路径 pre[v].push_back(u); // 记录新前驱 } else if (d[u] + G[u][v] == d[v]) { pre[v].push_back(u); // 添加等长路径的前驱 } ``` 3. **回溯所有路径** 使用DFS从终点反向遍历前驱列表,生成所有最短路径: ```cpp void dfs(int v, vector<int>& path) { if (v == start) { reverse(path.begin(), path.end()); allPaths.push_back(path); // 保存完整路径 return; } for (int u : pre[v]) { path.push_back(u); dfs(u, path); path.pop_back(); } } ``` --- #### 三、算法特性对比 | 特性 | 标准Dijkstra | 修改版Dijkstra | |---------------------|----------------------|----------------------| | **路径数量** | 仅1条 | 多条等长路径 | | **时间复杂度** | $O(|V|^2)$ | $O(|V|^2 + k|E|)$ | | **空间复杂度** | $O(|V|)$ | $O(|V| + k|P|)$ | | **应用场景** | 单一路径需求 | 需路径多样性分析 | > 注:$k$为实际最短路径数量,$|P|$为路径长度 --- #### 四、实例说明 考虑以下图结构(边权相同): ``` A / \ B → C \ / D ``` 起点A→终点D的最短路径: 1. `A → B → D` 2. `A → C → D` **标准算法**:可能只输出路径1或路径2 **修改版算法**:同时记录两条路径的前驱节点,通过DFS回溯输出全部路径[^3][^4] --- ### 总结 1. **标准Dijkstra算法无法直接找出多条最短路径**,因其贪心策略会固定最先找到的路径[^3]。 2. **通过扩展前驱列表+回溯DFS**,可获取所有等长最短路径,但需牺牲额外空间和时间复杂度。 3. 在交通规划、网络冗余分析等需路径多样性的场景中,修改版算法更具实用性[^5]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值