迪杰斯特拉(Dijkstra) —— 最短路算法

Dijkstra是最短路基础算法之一(还有判负环的SPFA和多源最短路的Floyd),但在正常情况下Dijkstra是最快的,也同样是最难打的其实都不是很难,接下来我们来谈谈具体算法:

1.适用范围

没有负环(就是走一圈就可以将路程变小,没有最短路的图)的单源最短路(就是只有一个起点的最短路);

2.基本思路:

已知量只有每条边的权值,但我们可以很容易的想到起点到起点的最短路是0,于是我们可以一开始就将定义一个dij数组来存从起点到每个点的最短路的长度,所以自然dij[1](假设1是起点)就为0,而想要更新其他的就要将除1以外的赋值为MAXN(一个极大值);

因为没有负的权值,所以很容易证明在已有的dij数组中的最小值是不会再被其他点更新的,所以每一次我们都将已有的最短的那个数对应的节点取出,再由它来更新它所连接的点(如果到最小值的那个点的值加上权值依然小于它所连接的那个节点的已有的最小值就更新它),同样不能忘了将这个最小值的点冻结(开个bool数组)以免重复计算;

由于每次取出一个点又冻住那个点,我们不难想到在有n个点的情况下只需要循环n次就行了;

3.实际操作:

输入:

先用邻接表存图(注意是单向图还是双向),记录起点终点;

初值:

先memset(dij,0x3f,sizeof(dij)),再将dij[1]赋值为0

Dijkstra:

用一个循环n次的for循环来做取点以及冻结的操作,每次取出最小的点再循环寻找每一个与它相连的节点并更新需要更新的节点;

输出:

按题目要求输出即可;

特殊处理(输出路径):

前面我们已经找到最短路,那么输出路径只需要再开一个数组,在更新最短路长度的同时记录是哪一个点更新的它就行了,最后用倒推的方式输出即可(可以类比背包问题找方案的方法);

 

4.例题:

骑车比赛

Description

小信准备去参加骑车比赛,比赛在 n 个城市间进行,编号从 1 到 n。选手们都从城市 1 出发,终点在城市 n。

已知城市间有 m 条道路,每条道路连接两个城市,注意道路是双向的。现在小信知道了他经过每条道路需要花费的时间,他想请你帮他计算一下,他这次比赛最少需要花多少时间完成。

Input

第一行输入两个整数 n,m(1≤n≤1,000,1≤m≤5,000),分别代表城市个数和道路总数。接下来输入 m 行,每行输入三个数字 a,b,c(1≤a,b≤n,1≤c≤200),分别代表道路的起点和道路的终点,以及小信骑车通过这条道路需要花费的时间。保证输入的图是连通的。

Output

输出一行,输出一个整数,输出小信完成比赛需要的最少时间。

Sample Input 1

    5 6
    1 2 2
    2 3 3
    2 5 5
    3 4 2
    3 5 1
    4 5 1

Sample Output 1

     6

Dijkstra经典例题就是个裸题,直接上代码

代码1(无优化)

#include<bits/stdc++.h>
using namespace std;
struct node//结构体代表每个节点的数据{
		int to,ti;//to代表到达的节点,ti代表所需时间
node(int a,int b) { //构造函数(不会可以不用)
	to=a;
	ti=b;
}
node() {}
};
int main() {
	int n,m,x,y,z;
	cin>>n>>m;
	map<int,vector<node> > ma;//用映射来存节点以及的其对应的边
	for(int i=1; i<=m; i++) {
		cin>>x>>y>>z;
		ma[x].push_back(node(y,z));//注意本题是双向图
		ma[y].push_back(node(x,z));
	}
	int st=1,dis[n+1],vis[n+1];//st代表目前最小的值的节点一开始当然是1
	memset(dis,0x3f,sizeof(dis));//先赋一个极大值
	memset(vis,0,sizeof(vis));//表示是否冻结了节点
	dis[1]=0;//起点为0;
	for(int j=1; j<=n; j++) { //循环取(冻结)点
		//cout<<st<<endl;
		int minn=1e+8,tmp;
		vis[st]=1;//冻结节点
		int l=ma[st].size();
		for(int i=0; i<l; i++) { //枚举、更新
			if(!vis[ma[st][i].to]) {
				dis[ma[st][i].to]=min(dis[ma[st][i].to],dis[st]+ma[st][i].ti)
			}
		}
		for(int i=1; i<=n; i++) { //找已有的最小值
			if(!vis[i]&&dis[i]<minn) {
				minn=dis[i];
				tmp=i;
			}
		}
		st=tmp;//更新
	}
	cout<<dis[n];
}

仔细读代码便可以发现,找最小值的操作会浪费大量时间(O(n)),那可不可以优化成O(1)呢??我们可以用一个神奇的数据类型——set(可参考基础数据结构),set有天然排序的性质,我们把这种优化叫做堆优化

堆优化

#include<bits/stdc++.h>
using namespace std;
struct node { //结构体代表每条边的数据
	int to,q,next;//to表示边连接的节点,q代表权值,next代表这条边的下一个边
	node(int a,int b,int c) {
		to=a;
		q=b;
		next=c;
	}
	node() {}
} ma[1000001]; //ma用于存边
set<pair<int,int> > st;//优化的set,分别存dij[i]和i
int k,head[1000001],dij[1000001],n,m;//head表示每一个节点,由head找到节点对应的边
bool ok[1000001];
void add(int a,int b,int c) { //用于存a到b用c时间的边
	ma[++k]=node(b,c,head[a]);
	head[a]=k;
}
int main() {
	memset(dij,0x3f,sizeof(dij));
	memset(head,-1,sizeof(head));
	cin>>n>>m;
	for(int i=1; i<=m; i++) {
		int x,y,z;
		cin>>x>>y>>z;
		add(x,y,z);
		add(y,x,z);
	}
	dij[1]=0;
	st.insert(make_pair(0,1));
	for(int i=1; i<=n; i++) {
		int u=st.begin()->second;//->为指针找到最小值的节点
		ok[u]=1;//冻结节点
		st.erase(st.begin());//删除节点
		for(int j=head[u]; ~j; j=ma[j].next) { //~j表示j是否为-1
			if(!ok[ma[j].to]&&dij[u]+ma[j].q<dij[ma[j].to]) {
				st.erase(make_pair(dij[ma[j].to],ma[j].to));
				dij[ma[j].to]=ma[j].q+dij[u];
				st.insert(make_pair(dij[ma[j].to],ma[j].to));
			}
		}
	}
	cout<<dij[n];
}

注:转载时有删改

--------------------- 
作者:Journey.R 
来源:CSDN 
原文:https://blog.csdn.net/GENE1997/article/details/83031595?utm_source=copy 
版权声明:本文为博主原创文章,转载请附上博文链接!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值