最短路径 - 迪杰斯特拉算法

题目描述

给一个 n ( 1 ≤ n ≤ 2500 ) n(1 \leq n \leq 2500) n(1n2500)个点 m ( 1 ≤ m ≤ 6200 ) m(1 \leq m\leq 6200 ) m(1m6200) 条边的无向图,求 s s s t t t的最短路。

输入格式

第一行四个由空格隔开的整数 n n n m m m s s s t t t
之后的 m m m 行,每行三个正整数 s i s_i si t i t_i ti w i ( 1 ≤ w i ≤ 1 0 9 ) w_i(1 \leq w_i \leq 10 ^ 9) wi(1wi109),表示一条从 s i s_i si t i t_i ti 长度为 w i w_i wi 的边。

输出格式

一个整数表示从 s s s t t t 的最短路长度。数据保证至少存在一条道路。

输入样例

7 11 5 4
2 4 2
1 4 3
7 2 2
3 4 3
5 7 5
7 3 3
6 1 1
6 3 4
2 4 3
5 6 3
7 2 1

输出样例

7

算法思路

迪杰斯特拉算法会计算出从起始节点到所有其他节点的最短距离

算法思维: 贪心策略

  • 邻接矩阵: g r a p h graph graph, 题解中用 a a a数组表示
  • 记录起点到所有节点的最短距离: d i s t dist dist
  • 记录节点的访问状态: v v v 0 0 0表示未访问, 1 1 1表示已访问
  • 进行 n n n次遍历, 每次在 d i s t dist dist中找到一个未被访问的最小值,
    • 记录最小值 m i n _ d i s t min\_dist min_dist, 并记录其节点 m i d mid mid
    • m i d mid mid节点为中转节点, 遍历 d i s t dist dist所有节点, 如果v[j] == 0 && dist[j] > dist[mid] + a[mid][j]节点未被访问, 且起始节点到 mid 节点的距离 + mid 节点到此节点的距离 < 起始节点到此节点的距离,也即是说找到了一条更短的起始点到达此节点的路径, 那么就更新 d i s t dist dist里此节点的信息.

代码实现

#include<bits/stdc++.h>
using namespace std;
int a[2505][2505]; // 邻接矩阵 
int v[2505] = {0}; // 访问数组, 初始为 0, 未访问 
int dist[2505]; // 起点到各节点的最短路径数组
int main()
{
	memset(a, 0x3f, sizeof(a)); // 初始为极大值
	memset(a, 0x3f, sizeof(a)); // 初始化为极大值
	int n, m, s, t;
	int x, y, d;
	cin >> n >> m >> s >> t;
	for(int i = 1; i <= m; i++) // 存储邻接矩阵 
	{
		cin >> x >> y >> d;
		a[x][y] = d; // 无向图, 相互距离一致 
		a[y][x] = d;  
	}
	for(int i = 1; i <= n; i++) // 记录已有的起始点 s 到各点的距离 
		dist[i] = a[s][i];
	v[s] = 1; // 起点标记 
	for(int i = 1; i <= n; i++) // 开始更新所有节点, n 次 
	{
		int min_dist = 0x3f3f3f3f; // 初始化最短距离 
		int mid =  0;
		for(int j = 1; j <= n; j++) // 寻找未被访问的最近邻接点 
		{
			if(v[j] == 0 && min_dist > dist[j])
			{
				min_dist = dist[j]; // 记录最近距离邻接点 
				mid = j;
			}
		}
		for(int j = 1; j <= n; j++) // 以最近距离邻接点为中转, 更新邻节点 
		{
			if(v[j] == 0 && dist[j] > dist[mid] + a[mid][j])
				dist[j] = dist[mid] + a[mid][j];
		}
		v[mid] = 1; // 当前节点标记为已访问 
	}
	cout << dist[t]; // 输出起点到 t 的最短路径 
}

输出最短的路径

  • 创建一个数组 p a t h path path , 初始化为 0 0 0, 记录每个节点的前驱节点, 即在路径最短的情况下: 起点到达该节点, 是由哪个节点中转过来的
  • 在更新最短路径的过程中, 如果更新了某个节点的最短距离, 那么相应的更新该节点的前驱节点。
  • 使用动态数组 p a t h _ s _ t path\_s\_t path_s_t记录起点至终点的最优路径, 由终点开始, 在 p a t h path path中回溯路径, 回溯至起点或者 0 0 0, 回溯完成。
  • 逆向输出动态数组, 即路径:

代码示例:

#include<bits/stdc++.h>
using namespace std;
int a[2505][2505]; // 邻接矩阵 
int v[2505] = {0}; // 访问数组, 初始为 0, 未访问 
int dist[2505]; // 起点到各节点的最短距离
int path[2505]; // 记录每个节点的前驱节点
int main()
{
	memset(a, 0x3f, sizeof(a));
	memset(dist, 0x3f, sizeof(dist));
	int n, m, s, t;
	int x, y, d;
	cin >> n >> m >> s >> t;
	for(int i = 1; i <= m; i++) // 存储邻接矩阵 
	{
		cin >> x >> y >> d;
		a[x][y] = d; // 无向图, 相互距离一致 
		a[y][x] = d;
	}
	for(int i = 1; i <= n; i++) // 起始点 s 到各点的距离 
		dist[i] = a[s][i];
	v[s] = 1; // 起点标记 
	for(int i = 1; i <= n; i++) // 开始更新所有节点, n 次 
	{
		int min_dist = 0x3f3f3f3f; // 初始化最短距离 
		int mid =  0;
		for(int j = 1; j <= n; j++) // 寻找未被访问的最近邻接点 
		{
			if(v[j] == 0 && min_dist > dist[j])
			{
				min_dist = dist[j]; // 记录最近距离邻接点 
				mid = j;
			}
		}
		for(int j = 1; j <= n; j++) // 以最近距离邻接点为中转, 更新邻节点 
		{
			if(v[j] == 0 && dist[j] > dist[mid] + a[mid][j])
				{
                    dist[j] = dist[mid] + a[mid][j];
				    path[j] = mid; // 记录此节点的前驱节点,即由哪个节点到达此节点最近
                }                
		}
		v[mid] = 1; // 标记已访问 
	}
	cout << dist[t]; // 输出到 t 的最短路径距离 
    // for(int i = 1; i <= n; i++) cout << endl << path[i]; // 查看所有节点的前驱节点
	vector <int> path_s_t; // 存储由终点至起点的路径
    int node = t; // 由终点开始回溯路径节点
    while(true)
    {
       path_s_t.push_back(node); // 将当前节点加入路径中
       if(path[node] == 0 ||  path[node] == s) // 当前节点无前置节点或前置节点是起点,路径存储结束
            {path_s_t.push_back(s); break;}
       node = path[node]; // 找到当前节点的前驱节点
    }
    // 输出完整路径, 因为存储的时是由终点到起点, 输出时, 逆向输出
    cout << endl;
    for(int i = path_s_t.size() - 1; i >= 0 ; i--) cout << path_s_t[i] << "->";	 
}
  • 22
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值