【算法】单源最短路径——dijkstra算法

一,概念

单源最短路径
给定一个带权有向图G=(V,E),其中每条边的权是一个实数。另外,还给定V中的一个顶点,称为源。要计算从源到其他所有各顶点的最短路径长度。这里的长度就是指路上各边权之和。这个问题通常称为单源最短路径问题。

dijkstra算法简介
迪杰斯特拉算法(Dijkstra),是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题。迪杰斯特拉算法主要特点是从起始点开始,采用贪心算法的策略,每次遍历到始点距离最近且未访问过的顶点的邻接节点,直到扩展到终点为止。

需要注意的是,dijkstra算法只能解决正权图问题,而要解决负权图,则需要另外一种办法SPFA算法,但是SPFA算法我还不熟,本次只讲dijkstra算法

二.分析dijkstra思想

假设有这么一个图
在这里插入图片描述
现在我们要求1到各个顶点的最短路径
1->1间的最短路径不用想了,肯定是0
1->2之间的最短路径很明显只有一条1->2,距离为2
1->3之间,有两条路径,一条是1->3,一条是1->2->3,因为1->3之间的距离为5,而1->2->3的距离为4,所以1->3的最短路径为4
1->4之间有三条路径,分别是1->2->4, 1->4, 1->3->4,而三条路线中,最短的那条线为1->2->4,距离为3
至此所有的最短路径就都求出来了

看完是不是发现dijkstra算法的思想也不是那么难呢?原理都知道了那么我们该怎么编写代码?

三.dijkstra的编写

dijkstra的步骤如下

  1. 初始化dis[start]=0
  2. 找出一个与start点距离dis最小的未确定最短路径的点x,标记它为已经确定的点
  3. 遍历所有以x为起点的边得到(x, y, d),如果dis[y] > dis[x] + d, 则更新dis[y] = dis[x] + d
  4. 重复2,3步直到所有的点都被标记为确定最短路径的点

dijkstra算法为什么正确?
当所有的边长为非负数的时候,全局最小值不可能被被其他结点更新,所以我们在第二步中从源点出发找dis值最小的值必然是与源点的最短路径,然后在不断的选择全局最小值来拓展,得到的最短路径必然正确

我们看下面这个图
在这里插入图片描述
我们一开始先将源点1加入已经确定的点(红点),然后将所有相连的点标记为待处理的点(绿点),更新每个dis值
在这里插入图片描述
然后我们从dis值最小的结点2开始拓展,此时必然为1->2的最短路径,标记点2为红点
在这里插入图片描述
然后将与结点2的所有点遍历,更新对应的dis值
在这里插入图片描述
从与点2距离最短的4开始拓展,因为此时距离已经最小,所以标记为红点
在这里插入图片描述
因为点4没有出边,所以直接跳回结点2,此时只有点3为待处理的点
在这里插入图片描述
至此源点到所有的点的最短路径都确定了

三.dijkstra的堆优化

按照上面的方法直接编写的话,时间复杂度会高达O(n^2),在某些情况可能会不够,那么我们是否有O(nlogn)算法的写法呢?通过观察,这时候我们可以发现,在第2步的查找与start点距离最小的点我们可以优化。那个用什么优化呢?

这时候我们可以使用堆去优化它。
可以定义一个优先队列q,设dis值小的优先级高,这样我们可以用q.top()来通过O(logn)的时间复杂度找到dis值最小的点,而原本查找最小dis值点需要的时间复杂度为O(n)

四.关于存图

通常来说,在算法比赛我们通常都是用链式前向星法来储存
这里简单说一下,就不扩展了

这是关于边的信息

int head[MAXN<<1];//head[i]表示以i为头结点的边的第一条边所在的位置
struct Edge {//在边(u,v)中
	int to;//to就是v
	int next;//next就是下一条(u,?)的位置
	int dis;//边(u,v)的长度
} e[MAXM<<1];//e[i]:第i条边的信息

这是存边的方式

void addEdge(int u,int v,int d)
{
	cnt++;//第cnt条边,初始值为0
	e[cnt].dis=d;//第cnt条边的权值
	e[cnt].to=v;//第cnt的出边
	e[cnt].next=head[u];//下一条边在e数组上的索引
	head[u]=cnt;//以u为起点的边,为最后一条存进的在e数组上的索引
}

五.例题

接下来让我们照惯例的写一下模板题来练练手吧,只看不写是永远学不会的!

传送门

描述:有一个邮递员要送东西,邮局在节点 1。他总共要送 n-1 样东西,其目的地分别是节点 2 到节点 n。由于这个城市的交通比较繁忙,因此所有的道路都是单行的,共有 m 条道路。这个邮递员每次只能带一样东西,并且运送每件物品过后必须返回邮局。求送完这 n-1 样东西并且最终回到邮局最少需要的时间。

输入格式
第一行包括两个整数,n 和 m,表示城市的节点数量和道路数量。

第二行到第 (m+1) 行,每行三个整数,u,v,w,表示从 u 到 v 有一条通过时间为 w 的道路。

输出格式
输出仅一行,包含一个整数,为最少需要的时间。

下面是题解

可能有的人一开始看这题目会有点蒙蔽,但其实仔细想想,送一件信我们都是从1->n,然后再从n->1返回吗?这其实不就是先求出1->n的最短路径,然后又求n->1的最短路径吗。所以只是正着来一次dijkstra,反着来一次dijkstra就行了

#include <iostream>
#include <queue>
#include <cstring>

using namespace std;
const int MAXN = 1000 + 5;
const int MAXM = 100000 + 5;
typedef long long LL;
struct Edge {
	int to;
	int next;
	int dis;
} e[MAXM<<1];
int head[MAXN<<1],vis[MAXN<<1],dis[MAXN<<1];
int n,m,cnt;

void addEdge(int u,int v,int d)
{
	cnt++;
	e[cnt].dis=d;
	e[cnt].to=v;
	e[cnt].next=head[u];
	head[u]=cnt;
}
//堆优化
struct node {
	int dis;
	int pos;
	bool operator < (const node& x)const { 
		return x.dis < dis;
	}
};

priority_queue<node>q;
//
void dijkstra(int s)//计算1到各点的最短路径
{
	memset(vis,0,sizeof(vis));
	for(int i=1; i<=n<<1; i++) {
		dis[i]=0x3fffffff;
	}
	dis[s]=0;
	q.push({0,s});
	while(!q.empty()) {
		node temp = q.top();
		q.pop();
		int x = temp.pos, d = temp.dis;
		if(vis[x])continue;
		vis[x]=1;
		for(int i=head[x]; i; i=e[i].next) {
			int y=e[i].to;
			if(dis[y]>dis[x]+e[i].dis) {
				dis[y]=dis[x]+e[i].dis;
				if(!vis[y]) {
					q.push({dis[y],y});
				}
			}
		}
	}
}



int main()
{
	cin>>n>>m;
	int u,v,d;
	for(int i=1; i<=m; i++) {
		scanf("%d %d %d",&u,&v,&d);
		addEdge(u,v,d);//1->n 
		addEdge(v+n,u+n,d);//n->1 //这样不需要开两个数组(
	}
	LL ans = 0;
	dijkstra(1);
	for(int i=1;i<=n;i++)ans+=dis[i];
	dijkstra(1+n);
	for(int i=1+n;i<=n<<1;i++)ans+=dis[i];
	printf("%lld\n",ans);
	return 0;
}

最后,欢迎提意见哦!觉得有帮助的话就支持一下我吧!

  • 59
    点赞
  • 192
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
离字典,将起始节点的距离设为0,其他节点的距离设为无穷大 distances = {node: sys.maxsize for node in graph} distances[start] = 0 # 初始化已访问节点的集合和未访以下是使用问节点D的集ijkstra合 visited = set() unvisited算法求解最短路径的Python = set(graph) while unvisited: # 代码示例: ```python class D选择当前ijkstra距: def __init__(self, graph离最小的节点 , start, current goal): self.graph = graph # 邻接表_node = min(unvisited, key=lambda self node: distances[node]) # 更新.start = start当前节点的 # 起邻居节点点 self.goal =的距离 goal # 终点 for neighbor in graph self.open[current_node]: _list = {} if neighbor in # open 表 self.closed_list unvisited: new_distance = distances[current_node] + = {} graph[current_node][neighbor # closed 表 self.open_list[start] if new_distance] = < distances[neighbor]: 0.0 # 将 distances[neighbor] = new_distance # 将当前起点放入 open_list 中 self.parent = {节点标记start:为已访 None} 问,并从未访问集合中移除 visited.add # 存储节点的父子关系。键为(current_node) 子节点, unvisited值为父.remove(current_node) return节点。方便做最 distances def print后_path(dist路径的ances,回 start溯 self.min, end): _dis = None # 根 # 最短路径的长度 def shortest_path据距离字典和终点节点(self): while True: ,逆向 if self打印路径.open_list is path = [end None: ] print('搜索 current_node =失败 end while current_node !=, 结束!') break distance start: , min_node = for neighbor in graph min(zip[current_node]: if(self.open_list distances[current.values(), self_node] ==.open_list.keys distances[neighbor())) #] + graph 取出距[neighbor][current_node]: 离最小的节点 self path.open_list.pop.append(min_node)(neighbor) current_node = neighbor break path.reverse() # 将其从 open_list 中去除 self print.closed("_list[minShortest_node] = path from", distance # 将节点加入 closed start, "to", end,_list ":", "->".join(path)) # 示例 中 if min_node == self.goal: # 如果节点为图的邻接矩阵终点 self.min_dis = distance 表示 graph shortest = { _path = [ 'Aself.goal]': {'B': # 5, 'C 记录从': 终1}, 点回溯的路径 'B
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值