Dijkstra堆优化之蓝桥王国

news2024/10/7 16:18:55

Dijkstra堆优化

Dijkstra算法是一种用于解决单源最短路径问题的算法,即从图中的一个顶点出发到所有其他顶点的最短路径。然而,处理大图时,常规的Dijkstra算法可能会遇到性能问题。这就是Dijkstra的堆优化算法派上用场的地方。在堆优化版本中,我们使用优先队列(通常是二叉堆)来获取当前未访问顶点中的最短距离顶点。这大大提高了算法的效率,因为每次从集合中获取最短距离的顶点的时间复杂度从O(V)降低到了O(logV),其中V是顶点的数量。

前置知识

图是一种非线性数据结构,由节点(或顶底)和边组成。有多种方式可以表示图的数据结构。常见的有两种图的存储方式:

邻接矩阵

邻接矩阵是一种二维数组,其中的每个元素表示两个顶点之间是否存在边。如果顶点 i 和顶点 j 之间存在边,则matrix[i][j] = 1,否则matrix[i][j] = 0

优点:

  • 表示简单,直观;
  • 对于无向图,邻接矩阵是对称的;
  • 适用于稠密图(边数接近顶点数平方的图)。

缺点

  • 对于稀疏图(边数远小于顶点数平方的图),邻接矩阵可能会浪费空间;
  • 需要O(n^2)的空间,其中 n 是顶点数。

邻接表

邻接表是一种数组和链表的混合数据结构。邻接表中的每个元素都是一个链表,表示与该顶点相连的所有顶点。

优点:

  • 适用于稀疏图,空间效率高;
  • 可以容易地找到与特定顶点相邻的所有顶点。

缺点:

  • 对于无向图,每条边都需要存储两次;
  • 需要更复杂的数据结构来存储和管理。

C++中的图可以使用STL的vector来表示:

/*邻接矩阵*/
#include <iostream>
#include <vector>

using namespace std;

int main(){
    // 创建一个5*5的邻接矩阵
    vector<vector<int> > adjMatrix(5, vector<int> (5, 0));
    
    // 添加边
    adjMatrix[0][1] = 1;
    adjMatrix[1][0] = 1;

    return 0;
}
/*邻接表*/
#include <iostream>
#include <vector>

using namespace std;

int main(){
    //创建一个邻接表
    vector<vector<int> > adjList(5);

    //添加边
    adjList[0].push_back(1);
    adjList[1].push_back(0);

    return 0;
}

链式前向星(更适合算法竞赛)

链式前向星是一种存储图的方式,它结合了邻接矩阵和邻接表的优点,通过两个一维数组来保存图的信息,空间负责度低,方便边的遍历,适合存储稀疏图。

链式前向星存储图的方式如下:

h[ ]数组:h[ver]表示节点 ver 的第一条边的编号;

e[ ]数组:e[i]表示编号为 i 的边的终点;

w[ ]数组:w[i]表示编号为 i 的边的权重;

ne[ ]数组:ne[I]表示编号为 i 的边的下一条边的编号。

在C++中,我们可以使用数组来表示链式前向星:

int h[N], e[M], w[M], ne[M], idx;

// 添加从a到b的边,初始时a指向空值
void add(int a, int b){
    // 分配空间idx,idx的ne[]指针指向初始时a指向的空值,头h[a]指向idx,idx更新
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

// 添加从a到b的边,权重为c
void add(int a, int b, int c){
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}

int main(){
    add(1, 2)

    memset(h, -1, sizeof h);

    // 输出节点1的所有邻居(i≠-1继续,否则就是指向空值了)
    for(int i = h[1]; ~i; i = ne[i]){
        int j = e[i];
        cout << j << ' ';
    }

    return 0;
}

图示:

优先队列入门

优先队列是一种抽象数据类型,它与常规队列非常相似,但在这种情况下,每个元素都有一定的优先级。在优先队列中,优先级最高的元素最先被删除。

在C++中,我们可以使用STL中的 priority_queue 来实现优先队列:

#include <iostream>
#include <queue>
using namespace std;

int main(){
	priority_queue<int> pq;//默认大数优先
	pq.push(30);
	pq.push(100);
	pq.push(25);
	pq.push(40);
	
	//显示优先对列
	while(!pq.empty()){
		cout << pq.top() << '\n';
		pq.pop();
	}
	return 0;
}

使用自定义排序规则的优先队列

有时我们需要根据特定的排序规则来构建优先对列,而不是默认的排序规则。在C++中,我们可以通过定义比较函数来实现自定义排序规则:

#include <iostream>
#include <queue>
using namespace std;

struct Compare{
	bool operator()(const int& a, const int& b){
		return a > b; //小数优先
	}
};

int main(){
	priority_queue<int, vector<int>, Compare> pq;//结构体为向量容器
	pq.push(30);
	pq.push(100);
	pq.push(25);
	pq.push(40);
	
	//显示优先对列
	while(!pq.empty()){
		cout << pq.top() << '\n';
		pq.pop();
	}
	return 0;
}

使用二元组的自定义排序规则的优先队列

在C++中,我们可以通过为pair定义比较函数来实现自定义排序:

#include <iostream>
#include <queue>
#define x first
#define y second
using namespace std;

struct Compare{
	bool operator()(const pair<int, int>& a, const pair<int, int>& b){
		return a.second < b.second; //第二个元素大优先
	}
};

int main(){
	priority_queue<pair<int, int>, vector<pair<int, int>>, Compare> pq;
	pq.push(make_pair(1,30));
	pq.push(make_pair(2,100));
	pq.push(make_pair(3,25));
	pq.push(make_pair(4,40));
	
	//显示优先队列
	while(!pq.empty()){
		cout << "("<< pq.top().first<<","<<pq.top().second<<")\n";
		pq.pop();
	}
	return 0;
}

朴素版

基本思想

Dijkstra算法是一种用于解决单源最短路径问题的贪心算法。其基本思想如下:

        1.初始化:初始时,源节点的最短路径长度设为0(dist[source] = 0)。对于所有其他节点,我们暂时将最短路径长度设为无穷大(dist[v] = ∞),表示我们还不知道到达它们的最短路径。同时我们创建一个空集合,用来存放已经确定最短路径的节点。

        2.选择最小的 dist 节点:在未被确定最短路径的节点中,选择一个距离最小的节点 u。由于最开始只有源节点的最短路径是已知的,所以在第一步中,我们会选择源节点。

        3.更新路径长度:然后,我们更新所有从 u 直接连线到的节点的最短路径长度。假设 v 是从 u 出发可以直接到达的一个结点,如果通过 u v 的路径 dist[u] + length(u, v) 比当前的 dist[v] 更短,那么我们就用新的路径长度来更新 dist[v]

        4.重复:我们将 u 添加到已确定最短路径的节点集合中。然后再去未确定最短路径的节点中找一个距离最小的节点,继续进行更新。重复这个步骤,直到所有的节点都被添加到已确定最短路径的节点集合中。

通过这种方式,Dijkstra算法保证了每次添加到集合的节点都是当前可以确定最短路径的节点。需要注意的是,Dijkstra算法不能处理存在负权边的图,因为这会导致它可能再添加节点到集合之前就已经确定了一个更短的路径。对于存在负权边的图,我们通常使用Bellman-Ford算法或者Floyd-Warshall算法。

/*伪代码*/
函数 Dijkstra()
	初始化 dist[] 为无穷大
	dist[1] = 0
	
	对于 i = 0 到 n-1
		t = -1
		对于 j = 1 到 n
			如果节点 j 还未被访问 并且 t 不存在或者 dist[j] 更小
				则 t = j //找到最短路径点
		标记 t 已被访问
		对于 j = 1 到 n
			更新 dist[j] 为 dist[j] 和 dist[t] + edge[t][j] 的最小值
		
	如果 dist[n] 仍然为无穷大,返回 -1
	否则,返回 dist[n]
			
/*C++*/
int dijkstra(){
	memset(dist, 0x3f, sizeof dist);
	dist[1] = 0
	
	for(int i = 0; i < n; i++){
		int t = -1;
		for(int j = 1; j <= n; i++)
			if(!st[j] && (t = -1 || dist[t] > dist[j]))
				t = j
		
		st[t] = true;
		for(int j = 1; j <= n; j++)
			dist[j] = min(dist[j], dist[t] + g[t][j]);
	}
	return dist[n] == 0x3f3f3f3f ? -1 : dist[n];
}

Dijkstra的堆优化算法的步骤如下:

        1.初始时,我们将起点的距离设为0,将所有其他顶点的距离设为无穷大(在代码中,这通常用一个大的数字表示)。我们将所有顶点放入优先队列中。

        2.然后,我们从优先队列中取出一个距离最短的顶点。我们将这个顶点标记为已访问。

        3.接下来,我们遍历从这个顶点出发的所有边。对于每条边,我们检查是否可以通过这条边到达的顶点的距离比原来顶点的距离路径会更短。如果可以,我们就更新这个顶点的距离,并在优先队列中更新这个顶点的位置。

        4.我们重复上述步骤,直到优先队列为空,即所有可达的顶点都已访问过,或者找到目标顶点的最短路径。

假设我们有一个图,它包含了5个顶点(1, 2, 3, 4, 5)和6条边。边的信息如下:1-2(权重6),1-3(权重3),2-3(权重2),3-4(权重1),2-4(权重5),4-5(权重2)。如果我们要找到从顶点 1 到所有其他顶点的最短路径,我们可以按照以下步骤进行:

        1.将所有顶点的距离初始化为无穷大,除了起点1,它的距离为0。所以,优先队列为:(1,0)(2,∞)(3,∞)(4,∞)(5,∞)。

        2.从队列中取出距离最小的顶点,这是1。然后,我们查看所有从1开始的边,我们发现顶点2和3的距离可以更新。所以,优先队列更新为:(2,6)(3,3)(4,∞)(5,∞)。

        3.我们再次从队列中取出距离最小的顶点,这是3。然后,我们查看从3开始的边,我们发现顶点2和4的距离可以更新。所以,优先队列更新为:(2,5)(4,4)(5,∞)。

        4.我们继续这个过程,直到队列为空。最后,我们得到了从顶点1到所有其他顶点的最短距离:1-2(距离5),1-3(距离3),1-4(距离4),1-5(距离6)。

代码

/*伪代码*/
设定顶点数 n,边数 m
设定存储图的数组 h[N], e[N], w[N], ne[N], 初始化索引 idx
设定存储最短距离的数组 dist[N]
设定标记数组 st[N]

定义函数 dijkstra:
	初始化 dist 数组为无穷大
	建立优先队列 q, 队列元素为一对整数(距离, 顶点), 按距离从小到大排序
	
	设定起点 1 的距离为 0
	将(0, 1)推入队列 q
	
	当队列 q 不为空时:
		弹出队列顶部元素 t,设定顶点 ver 和距离 distance
		
		如果顶点 ver 已经被标记过,则继续下一轮循环
		否则,标记顶点 ver
		
		遍历顶点 ver 的所有领边:
			设定邻边的目标顶点为 j
			如果到达 j 的距离大于到达 ver 的距离加上边的权重w[i],则更新
到达 j 的距离,并将(dist[j], j)推入队列 q
			
	如果终点 n 的距离为无穷大,返回-1
	否则,返回终点 n 的距离
/*C++*/
int n, m;
const int N = 1e3 + 10;
int h[N], e[N], w[N], ne[N], idx;
int dist[N];
bool st[N];
typedef pair<int, int> PII;

int dijkstra(){
	memset(dist, 0x3f, sizeof dist);
	priority_queue<PII, vector<PII>, greater<PII>> q;
	
	dist[1] = 0;
	q.push({0, 1});
	
	while(!q.empty()){
		auto t = q.top();
		q.pop();
		auto ver = t.y, distance = t.x;
		
		if (st[ver])
			continue;
		st[ver] = true;
		//h[]第一条边,ne[]下一条边,e[]边指向的终点,w[]边的权重
		for(int i=h[ver]; i!=-1; i=ne[ver]){
			int j = e[i];
			if(dist[j] > dist[ver] + w[i]){
				dist[j] = dist[ver] + w[i];
				q.push({dist[j], j});
			}
		}
	}
	return dist[n] == 0x3f3f3f3f ? -1 : dist[n];
}

蓝桥王国

题目描述

小明是蓝桥王国的王子,今天是他登基之日。在即将成为国王之前,老国王给他出了道题,他想要考验小明是否有能力管理国家。内容如下:

蓝桥王国一共有N个建筑和M条单向道路,每条道路都连接这两个建筑,每个建筑都有自己编号,分别为1~N。(其中皇宫的编号为1)

国王想让小明回答从皇宫到每个建筑的最短路径是多少,但紧张的小名此时已经无法思考,请你编写程序帮助小明回答国王的考核。

输入描述

输入第一行包含两个正整数N, M。

第2到M+1行每行包含三个正整数u, v, w,表示u→v之间存在一条距离为w的路。

1\leqslant N\leqslant 3\times 10^5, 1\leqslant M\leqslant 10^6,1\leqslant u_i,v_i\leqslant N, 1\leqslant w_i\leqslant 10^9

输出描述

输出仅一行,共N个数,分别表示从皇宫到编号为1~N建筑的最短距离,两两之间用空格隔开。(如果无法达到则输出-1)

输入输出样例

示例 1

输入

3 3 
1 2 1
1 3 5
2 3 2

输出

0 1 3

运行限制

语言最大运行时间最大运行内存
C++2s512M
C2s512M
Python32s512M
Java2s512M

代码

#include <iostream>
#include <queue>
#include <cstring>    //memset
#include <algorithm>

#define x first
#define y second

using namespace std;
typedef long long LL;
typedef pair<LL, int> PLI;

int n, m;
const int N = 3e5 + 10, M = 1e6 + 10;
//节点的第一条边,指向的终点,下一条边
int h[N], e[M], ne[M], idx;
LL w[M], dist[N];
bool st[N];

void dijkstra(){
	memset(dist, 0x3f, sizeof dist);
	dist[1] = 0;
	
	priority_queue<PLI, vector<PLI>, greater<PLI>> heap;
	heap.push({0,1});
	
	while(!heap.empty()){
		auto t = heap.top();
		heap.pop();
		
		LL ver = t.y, distance = t.x;
		if(st[ver])
			continue;
		st[ver] = true;
		
		for(int i = h[ver]; i != -1; i = ne[i]){
			LL j = e[i];
			
			if(dist[j] > dist[ver] + w[i]){
				dist[j] = dist[ver] + w[i];
				heap.push({dist[j], j});
			}
		}
	}
}

void add(LL a, LL b, LL c){
	//分配内存idx, 插入链表(头插法), 加权重
	e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}

int main(){
	cin >> n >> m;
	//初始化链表指向空值-1
	memset(h, -1, sizeof h);
	
	for(int i = 0; i < m; i++){
		LL a, b, c;
		cin >> a >> b >> c;
		
		add(a, b, c);//放入链表
	}
	dijkstra();
	
	for(int i = 1; i <= n; i++){
		cout << (dist[i]==0x3f3f3f3f3f3f3f3f ? -1:dist[i]) << " ";
	}
	cout << endl;
	
	return 0;
}

总结

Dijkstra算法是最短路径的一个重要解法,但是只能用于正值带权。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1560801.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【小黑送书—第十八期】>>让工作自动化起来!无所不能的Python(文末送书)

随着我国企业数字化和信息化的深入&#xff0c;企业对办公自动化的效率和灵活性要求越来越高。Python作为一种开源的软件应用开发方式&#xff0c;通过提供强大丰富的库文件包&#xff0c;极大地简化了应用开发过程&#xff0c;降低了技术门槛。Python开发有哪些优势、挑战以及…

如何查找合适自己的EI期刊和会议?

大家都知道EI工程索引包含期刊和会议&#xff0c;两者含金量都是比较高的&#xff0c;那么如何才能找到适合自己的EI期刊和会议?ei期刊数量众多&#xff0c;ei国际会议举办次数也是很多的&#xff0c;下面分享几种查找的渠道仅供参考&#xff1a; 渠道一、通过搜索引擎查找&am…

【蓝桥杯第十三届省赛B组】(详解)

九进制转十进制 #include <iostream> #include<math.h> using namespace std; int main() {cout << 2*pow(9,3)0*pow(9,2)2*pow(9,1)2*pow(9,0) << endl;return 0; }顺子日期 #include <iostream> using namespace std; int main() {// 请在此…

【Emgu CV教程】10.11、MatchShapes()比较轮廓的相似度

文章目录 一、函数介绍二、演示1.原始素材2.代码3.运行结果 一、函数介绍 MatchShapes()函数&#xff0c;可以比较两个轮廓的相似度&#xff0c;而且对于旋转、放大、缩小的轮廓都能适用&#xff0c;利用这个函数就能实现最简单的物体检测。函数官方定义如下 public static d…

【机器学习】代价函数

&#x1f388;个人主页&#xff1a;豌豆射手^ &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f917;收录专栏&#xff1a;机器学习 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共同学习、交流进…

Web应急响应

2024年护网将至&#xff0c;最近我将分享一些红蓝对抗的一些技巧&#xff0c;应急响应、信息收集相关的知识概念以及相关技巧。 目录 1. 黑客攻击流程 2. webshell流量特征 1.1.菜刀特征 1.2.冰蝎3.0 &#xff1a; 1.3.冰蝎2.0&#xff1a; 1.4.冰蝎3.11流量特征 1.5.蚁…

申请小程序https证书

背景&#xff1a;目前小程序在开发阶段是必须要使用SSL证书实现服务器https访问&#xff0c;在2017年12月30后http将无法调用微信公众平台接口&#xff0c;需要上线微信小程序的个人或企业&#xff0c;需要办理SSL证书&#xff0c;才能实现网站HTTPS化&#xff0c;让小程序顺利…

用python做一个终身免费的听书工具,一文搞定!!!

你好&#xff0c;小编有多年大厂经验&#xff0c;努力构建通俗易懂的、好玩的编程语言教程。 欢迎关注&#xff01;你会有如下收益&#xff1a; 了解大厂经验拥有和大厂相匹配的技术等 希望看什么&#xff0c;评论或者私信告诉我&#xff01; 一、前言 话说某天&#xff0c…

SCTNet 项目排坑

SCTNet 项目排坑 任务过程记录在旧环境下运行重配环境训练测试速度测试 任务 想跑一下最新的实时分割代码。由于这个项目也是基于mmsegmentation的&#xff0c;所以我想先看看之前那个环境是否能直接适配。不行&#xff0c;我再新建环境。 过程记录 在旧环境下运行 必要工作…

16-代码检查:如何进行静态代码检查?

在做Go项目开发的过程中&#xff0c;我们肯定需要对Go代码做静态代码检查。虽然Go命令提供了go vet和go tool vet&#xff0c; 今天我想给你介绍的golangci-lint&#xff0c;是目前使用最多&#xff0c;也最受欢迎的静态代码检查工具 接下来&#xff0c;我就从golangci-lint…

【数据结构】——树和二叉树相关概念(全网超级详解)

创作不易&#xff0c;家人们来一波三连吧&#xff1f;&#xff01; 前言 世界上最大的树--雪曼将军树&#xff0c;这棵参天大树不是最长也不是最宽&#xff0c;是不是很奇怪&#xff0c;大只是他的体积是最大的&#xff0c;看图片肯定是感触不深&#xff0c;大家可以自己去看…

go入门到精通

初识Go语言 Go语言介绍 Go语言是什么 2009年11月10日&#xff0c;Go语言正式成为开源编程语言家庭的一员。 Go语言&#xff08;或称Golang&#xff09;是云计算时代的C语言。Go语言的诞生是为了让程序员有更高的生产效率&#xff0c;Go语言专门针对多处理器系统应用程序的编…

vue2项目安装(使用vue-cli脚手架)

使用npm安装 安装镜像&#xff08;使npm创建项目更快&#xff09;&#xff1a;镜像可更换 npm config set registry https://registry.npmmirror.com1.全局安装vue-cli&#xff08;一次&#xff09; npm install -g vue/cli 2. 查看vue-cli 版本 vue --version 3. 创建项目…

【Pytorch学习笔记(二)】张量的创建(补充)

一、知识回顾 我们在博客《张量的创建与访问》中已经讨论了一些张量的创建方法如torch.CharTensor()、torch.FloatTensor()以及torch.zeros()等张量创建方法&#xff0c;但由于其仅仅介绍了cpu版本torch下张量的创建方法和只有具体数据类型张量&#xff0c;本节内容旨在补充gp…

数字示波器

数字示波器 综述&#xff1a;本文讲述了数字示波器的电路组成。 一&#xff0e;定义 显示电信号波形的仪器 二&#xff0e;组成 由模拟前端处理电路、电源电路、单片机电路、控制电路、触发电路、校准电路组成。 1&#xff09;模拟前端处理电路 将输入的模拟信号处理后传…

2024中国医药企业项目管理大会将于7月在京召开

“创新是企业之魂”&#xff0c;对于医药企业来说药品创新研发能力很大程度上决定了公司核心竞争力和可持续发展能力。新药研发具有高投入、高成本、高风险、高收益、长周期等特点&#xff0c;从药物的发现研发到临床试验到获批生产上市销售是一个充满风险挑战的较为漫长历程&a…

判断点在多边形内的算法

在计算几何中&#xff0c;判定点是否在多边形内&#xff0c;是个非常有趣的问题。通常有两种方法&#xff1a; 一、Crossing Number&#xff08;交叉数&#xff09; 它计算从点P开始的射线穿过多边形边界的次数。当“交叉数”是偶数时&#xff0c;点在外面;当它是奇数时&…

基于8086毫秒数码管计时器仿真设计

**单片机设计介绍&#xff0c;基于8086毫秒数码管计时器仿真设计 文章目录 一 概要二、功能设计三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于8086毫秒数码管计时器仿真设计概要主要关注于利用8086微处理器实现毫秒级别的计时功能&#xff0c;并通过数码管显示时间…

action method

package cn.hello01;import com.opensymphony.xwork2.Action; import com.opensymphony.xwork2.ActionSupport;public class UserAction extends ActionSupport{//增加public String save(){System.out.println("保存");return Action.SUCCESS;}//删除public String …