Dijkstra算法图文详解

本文深入讲解了Dijkstra算法,一种基于贪心思想的最短路径算法。通过详细步骤解析和代码实现,帮助读者理解如何从一个源点找到到图中其他所有点的最短路径。

Dijkstra算法

Dijkstra算法算是贪心思想实现的,首先把起点到所有点的距离存下来找个最短的,然后松弛一次再找出最短的,所谓的松弛操作就是,遍历一遍看通过刚刚找到的距离最短的点作为中转站会不会更近,如果更近了就更新距离,这样把所有的点找遍之后就存下了起点到其他所有点的最短距离。

问题引入:

指定一个点(源点)到其余各个顶点的最短路径,也叫做“单源最短路径”。例如求下图中的1号顶点到2、3、4、5、6号顶点的最短路径。

 

 下面我们来模拟一下:

 这就是Dijkstra算法的基本思路:

接下来是代码:

已经把几个过程都封装成了基本模块:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#define Inf 0x3f3f3f3f

using namespace std;

int map[1005][1005];

int vis[1005],dis[1005];
int n,m;//n个点,m条边

void Init ()
{
	memset(map,Inf,sizeof(map));
	for(int i=1;i<=n;i++)
	{
		map[i][i]=0;
	}
}

void Getmap()
{
	int u,v,w;
    for(int t=1;t<=m;t++)
	{
	  	scanf("%d%d%d",&u,&v,&w);
	  	if(map[u][v]>w)
		  {
		  map[u][v]=w;
		  map[v][u]=w;
	      }
	}	
	
}

void Dijkstra(int u)
{
	memset(vis,0,sizeof(vis));
	for(int t=1;t<=n;t++)
	{
		dis[t]=map[u][t];
	}
	vis[u]=1;
	for(int t=1;t<n;t++)
	{
		int minn=Inf,temp;
		for(int i=1;i<=n;i++)
		{
			if(!vis[i]&&dis[i]<minn)
			{
				minn=dis[i];
				temp=i;
			}
		}
		vis[temp]=1;
		for(int i=1;i<=n;i++)
		{
			if(map[temp][i]+dis[temp]<dis[i])
			{
				dis[i]=map[temp][i]+dis[temp];
			}
		}
	}
	
}

int main()
{
	
	scanf("%d%d",&m,&n);
	Init();
	Getmap();
	Dijkstra(n);
	printf("%d\n",dis[1]);
	
	
	return 0;
}

<think>我们被要求详细解释Dijkstra算法的实现步骤。根据引用[1]和[3],我们知道Dijkstra算法是一种用于计算图中单源最短路径的贪心算法。它适用于有向图或无向图(但边权必须非负)。实现步骤详解:1.初始化:设置一个数组dist[],用于存储从源点到各个顶点的最短路径估计值。初始时,将源点的距离设为0(dist[source]=0),其他所有顶点的距离设为无穷大(表示尚未到达)。同时,创建一个集合(或数组)visited[],用于标记哪些顶点已经找到了最短路径(初始时都未访问)。2.循环以下步骤,直到所有顶点都被访问:a.从未访问的顶点中选择一个距离最小的顶点u(即dist[u]最小)。b.将顶点u标记为已访问(表示从源点到u的最短路径已确定)。c.遍历顶点u的所有邻居顶点v:对于每个邻居v,如果通过u到达v的路径长度(即dist[u]+weight(u,v))小于当前记录的dist[v],则更新dist[v]为这个更小的值(这一步骤称为“松弛”操作)。3.当所有顶点都被访问后,dist数组中存储的就是从源点到各个顶点的最短路径长度。注意:在步骤2a中,选择最小距离的顶点时,可以使用优先队列(最小堆)来优化,以提高效率。如果不使用优先队列,则需要线性扫描所有未访问顶点,这是O(n)的时间复杂度;而使用优先队列(如二叉堆)可以将这一步优化到O(logn)。根据引用[2]的代码示例,我们可以更具体地看到步骤:-初始化:设置dis数组(即dist数组),源点dis[u]=0,其他为Inf(无穷大)。vis数组(visited)初始全0。-然后循环n-1次(因为每次处理一个顶点,源点已经处理,剩下n-1个):在未访问顶点中寻找dis最小的顶点(temp)。将其标记为已访问(vis[temp]=1)。遍历该顶点的所有邻居(在代码中,由于使用邻接矩阵,所以遍历所有顶点,通过map[temp][i]判断是否有边):如果邻居i未访问,且通过temp到达i的路径(dis[temp]+map[temp][i])比当前dis[i]小,则更新dis[i]。但是,引用[2]的代码中,更新邻居时遍历了所有顶点(而不仅仅是邻居),这是因为其使用邻接矩阵存储图,所以需要检查每个顶点(但非邻居的边权为无穷,不会更新)。如果使用邻接表,则只需遍历邻居。引用[3]中给出了一个具体的例子:从V1到V3的最短路径是10,然后从V3到V4的边权为50,那么V1到V4的路径就可以更新为10+50=60(如果原来V1到V4的距离大于60,则更新)。因此,我们详细步骤总结如下:步骤1:初始化dist[source]=0for每个顶点v(除了source):dist[v]=∞visited[]=全false步骤2:创建优先队列(可选优化)并将源点加入(如果不使用优先队列,则跳过队列操作,直接线性查找)步骤3:当存在未访问顶点:选择未访问顶点中dist最小的顶点u(如果使用优先队列,则从队列中取出最小元素)标记u为已访问对于u的每个邻居v(且v未访问):新的距离=dist[u]+边权(u,v)如果新的距离<dist[v]:dist[v]=新的距离如果使用优先队列,更新队列中v的优先级(或者重新插入)步骤4:结束,输出dist数组注意:在更新邻居时,即使使用优先队列,如果队列不支持减少键操作,也可以将新的距离值插入队列(同一个顶点可能有多个距离值,但我们只取最小的那个,因此当从队列中取出一个顶点时,需要检查它是否已经过时(即当前dist值已经比队列中存储的小))。下面我们用一个简单的例子(引用[3]中的例子)说明:图:顶点V1,V2,V3,V4边:V1->V2(权重10),V1->V3(权重30),V2->V3(权重10),V3->V4(权重50)求V1到各点的最短路径。初始化:dist[V1]=0,其他为∞。选择V1(最小dist):标记V1访问。更新邻居:V2:0+10=10(小于∞,更新为10);V3:0+30=30(更新为30)。接下来未访问顶点中最小的是V2(dist=10):标记V2访问。更新V2的邻居:V3:10+10=20(小于当前30,所以更新为20)。然后选择V3(dist=20):标记V3访问。更新V4:20+50=70(更新V4为70)。最后选择V4(没有未访问的邻居,结束)。因此,从V1到各点的最短路径:V1:0,V2:10,V3:20,V4:70。引用[1]指出该算法在路由算法、网络优化、地图导航等领域有广泛应用。现在,我们按照要求生成相关问题:1.Dijkstra算法如何处理有负权边的图?2.为什么Dijkstra算法使用贪心策略?它如何保证得到的是最短路径?3.Dijkstra算法的时间复杂度是多少?如何优化?注意:我们引用了三个参考文献,在回答中适当添加引用标识。</think>### Dijkstra算法实现步骤详解 Dijkstra算法用于求解**单源最短路径问题**(从固定起点到图中所有其他顶点的最短路径),其核心是**贪心策略**:每次选择当前距离起点最近的未处理顶点,通过该顶点更新邻居顶点的距离。以下是逐步实现说明(以有向图/无向图为例,边权非负): --- #### **步骤1:初始化** - 创建距离数组 `dist[]`:存储起点到每个顶点的最短距离估计值 - 起点 `s` 的 `dist[s] = 0` - 其他顶点 `dist[i] = ∞`(表示暂不可达) - 创建访问标记数组 `visited[]`:标记顶点是否已确定最短路径(初始全为 `false`) - (可选)创建前驱数组 `prev[]`:记录最短路径上的前驱顶点(用于回溯路径) **示例**: 起点为 $V_1$ 时: $dist = [0, \infty, \infty, \infty]$ $visited = [false, false, false, false]$ --- #### **步骤2:循环处理顶点** 重复以下操作,直到所有顶点被访问: 1. **选择未访问的最近顶点** 在未访问顶点中选取 `dist[u]` 最小的顶点 $u$ *(若使用优先队列优化,可降低时间复杂度)* 2. **标记顶点为已访问** `visited[u] = true`(此时 $u$ 的最短路径已确定) 3. **松弛操作(更新邻居距离)** 遍历 $u$ 的所有**未访问邻居** $v$: - 计算新路径长度:`new_dist = dist[u] + weight(u, v)` - 若 `new_dist < dist[v]`,则更新: `dist[v] = new_dist` `prev[v] = u`(记录路径) --- #### **步骤3:算法结束** - `dist[]` 存储起点到所有顶点的最短路径长度 - 通过 `prev[]` 回溯路径(例如:$V_4 \to V_3 \to V_1$) --- ### **图文示例解析** 以引用[3]中的图为例: ![](https://example.com/dijkstra-graph.png) - **顶点**:$V_1, V_2, V_3, V_4$ - **边权**:$V_1 \to V_2=10$, $V_1 \to V_3=30$, $V_2 \to V_3=10$, $V_3 \to V_4=50$ **执行过程**: | 当前顶点 | 更新操作 | `dist[]` 变化 | |----------|------------------------------|----------------------------| | $V_1$ | 更新 $V_2=10$, $V_3=30$ | $[0,10,30,\infty]$ | | $V_2$ | 更新 $V_3=\min(30,10+10)=20$ | $[0,10,20,\infty]$ | | $V_3$ | 更新 $V_4=20+50=70$ | $[0,10,20,70]$ | | $V_4$ | 无未访问邻居,结束 | | **结果**: - $V_1 \to V_2$:$10$ - $V_1 \to V_3$:$20$(路径 $V_1 \to V_2 \to V_3$) - $V_1 \to V_4$:$70$(路径 $V_1 \to V_2 \to V_3 \to V_4$) --- ### **代码实现关键** 参考引用[2]的C语言核心逻辑: ```c void Dijkstra(int source) { // 初始化dist数组 for (int i = 1; i <= n; i++) { dist[i] = INF; visited[i] = false; } dist[source] = 0; // 遍历所有顶点 for (int i = 1; i <= n; i++) { // 步骤2.1:选择未访问的最近顶点u int u = -1, min_dist = INF; for (int j = 1; j <= n; j++) { if (!visited[j] && dist[j] < min_dist) { min_dist = dist[j]; u = j; } } if (u == -1) break; // 无剩余顶点 visited[u] = true; // 步骤2.2:标记已访问 // 步骤2.3:松弛邻居 for (int v = 1; v <= n; v++) { if (!visited[v] && dist[u] + graph[u][v] < dist[v]) { dist[v] = dist[u] + graph[u][v]; prev[v] = u; // 记录路径 } } } } ``` --- ### **关键注意事项** 1. **边权非负**:若存在负权边,需使用Bellman-Ford算法[^1]。 2. **时间复杂度**: - 基础实现:$O(n^2)$($n$ 为顶点数) - 优先队列优化:$O(m \log n)$($m$ 为边数) 3. **空间复杂度**:$O(n^2)$(邻接矩阵)或 $O(n+m)$(邻接表) --- ### **典型应用场景** - 地图导航(最短行车路线) - 网络路由协议(OSPF) - 社交网络中的亲密关系分析 - 物流路径规划[^1] ---
评论 98
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

black-hole6

你的鼓励将是我创作的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值