关于dijkstra的优化 及 多源最短路
先来看这样一道题目
给你N个点,M条双向边,要求求出1号点到其他所有点的距离。其中 2 <= N <= 1e5, 1 <=M <= 1e6.
对于这样的一道题目 我们当然不可能开一个数组edge[N][N]来记录边的信息,根本不可能开的下。
假如开下了也会有很多边为-1,浪费了很多空间。 所以可以对存边的方式进行优化。
优化1: 对边进行优化。
因为edge[N][N]的空间需要N^2大小,当N稍微大一点点的时候,就没办法开这么大的空间。
并且由于当边的分布比较散的时候,我们每次找到一个新的最小点之后,都要遍历他所有的边去更新d[]数组,
然而当边分布分散的时候,我们会花大部分时间在不存在的边上。
在这里我们用链式前向星来存边,这样可以使得存边的空间大大减小,并且每次更新的时候遍历的边都是真正存在的边,不会在访问那些不存在的边。
链式前向星优化
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = ;
const int M = * ;
const int inf = 0x3f3f3f3f;
int d[N];
bool vis[N];
int head[N];
int nt[M], to[M], w[M];
int tot;
void init(){
memset(head, -, sizeof(head));
tot = ;
}
void add(int u, int v, int val){
to[tot] = v;
w[tot] = val;
nt[tot] = head[u];
head[u] = tot++;
}
int main()
{
int n, m;
while(~scanf("%d%d",&n,&m)&& (n || m)){
int a,b,c;
init();
memset(d, inf, sizeof(d));
memset(vis, , sizeof(vis));
while (m--){
scanf("%d%d%d", &a, &b, &c);
add(a,b,c);
add(b,a,c);
}
d[] = ;
while (){
int min1 = inf,z = -;
for (int j = ;j <= n; j++)
if(!vis[j] && min1 > d[j])
z = j, min1 = d[j];
if(z == -) break;
vis[z] = ;
for (int j = head[z]; ~j; j = nt[j]){
d[to[j]] = min(d[to[j]], d[z] + w[j]);
}
}
printf("%d\n", d[n]);
}
return ;
}
当然,也可以用vector来存边的信息
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = ;
const int inf = 0x3f3f3f3f;
int d[N];
bool vis[N];
struct Node{
int to;
int w;
};
vector<Node> G[N];
int main()
{
int n, m;
while(~scanf("%d%d",&n,&m)&& (n || m)){
int a,b,c;
for(int i = ; i <= n; i++)
G[i].clear();
memset(d, inf, sizeof(d));
memset(vis, , sizeof(vis));
while (m--){
scanf("%d%d%d", &a, &b, &c);
G[a].push_back({b,c});
G[b].push_back({a,c});
}
d[] = ;
while (){
int min1 = inf,z = -;
for (int j = ;j <= n; j++)
if(!vis[j] && min1 > d[j])
z = j, min1 = d[j];
if(z == -) break;
vis[z] = ;
for(int j = ; j < G[z].size(); j++){
int v = G[z][j].to, dis = G[z][j].w;
d[v] = min(d[v], d[z] + dis);
}
}
printf("%d\n", d[n]);
}
return ;
}
我个人还是更喜欢用链式前向星,虽然要写add可是链式前向星的的常数小一点。
经过优化之后,他的复杂度就变成了 N*N + 2*E,虽然还是N^2级别,可是他的常数比没优化前的小。
当然对于开头的那个题目来说,我们可以存下边的信息了,但是N^2的复杂度还是没办法接受的。
优化2:对时间进行优化(需要先明白优先队列)
我们发现 在优化1后的代码实现中,我们需要 1 找到d最小的点 2用最小的点去更新d数组。3 重复1->2的过程,直到所有的点都不会发生改变。
对于操作2来说,我们进行了优化1之后,操作2做的已经是最优了,他所干的事情没有一个是没意义的,
对于操作3来说,从dijkstra来说,只有进行了n次之后,才能保证每个点都到了最短的距离。
所以我们只能优化操作1,找到d[]值最小的点。
在这里我们使用优先队列对于时间进行。
#define pll pair<int,int>
priority_queue<pll,vector<pll> ,greater<pll> >que;
que.push(pll(0,1));//左边为dis 右边为点
优先队列本来是优先把大的元素放在顶上,我们可以使用top()函数来获取优先队列的优先级最高的元素。
优先队友可以自定义优先级,在这里,我们将优先级定义为
pair的第一维越小就在队列的最前面,我们把距离放在第一维,把点放在第二维。
这样每次我们从优先队列中取出一个pair,都是队列中离原点距离最小的点了。
这样我们只需要lgn的复杂度就可以找到最小的那个点了,而不是每次都n的代价扫一遍d[]的数组找到最小的那个点了。
当我们每次找到一个点之后,假设找到点为u,我们都先判断一下u 是不是被标记过了,如果被标记过了,那么我就继续再找下一个点。
如果没有被标记过,那么我们就从u点出发,看一下附近的点能不能通过这个点出发使得他的d更小。
加入现在存在点v, d[v] > d[u] + w。那么我们就更新d[v],然后把 pll(d[v], v)放进队列中,等待选取。
直到优先队列为空,那么就结束更新。
代码:
#include<bits/stdc++.h>
using namespace std;
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
typedef pair<int,int> pll;
using namespace std;
const int N = ;
const int M = * ;
const int inf = 0x3f3f3f3f;
int d[N];
bool vis[N];
int head[N];
int nt[M], to[M], w[M];
int tot;
void init(){
memset(head, -, sizeof(head));
tot = ;
}
void add(int u, int v, int val){
to[tot] = v;
w[tot] = val;
nt[tot] = head[u];
head[u] = tot++;
}
void dijkstra(){
priority_queue<pll,vector<pll> ,greater<pll> >que;
que.push(make_pair(,));//左边为dis 右边为点
while(!que.empty()){
pll temp = que.top();
que.pop();
int dis = temp.first;
int u = temp.second;
//if(dis != d[u]) continue;
if(vis[u]) continue;
vis[u] = ;
for(int i = head[u]; ~i; i = nt[i]){
int v = to[i];
if(d[v] > d[u] + w[i]){
d[v] = d[u] + w[i];
que.push(pll(d[v],v));
}
}
} }
int main()
{
int n, m;
while(~scanf("%d%d",&n,&m)&& (n || m)){
int a,b,c;
init();
memset(d, inf, sizeof(d));
memset(vis, , sizeof(vis));
while (m--){
scanf("%d%d%d", &a, &b, &c);
add(a,b,c);
add(b,a,c);
}
d[] = ;
dijkstra();
printf("%d\n", d[n]);
}
return ;
}
代码中有一个被注释的地方
if(dis != d[u]) continue;
我们可以用这一句话代替vis数组。
假设优先队列中存在 pll(10,5) pll(100,5) 2个pair, 通过上面我们可以知道, 第一个肯定是先把(10,5)的这一个pair取出来,并且d[5] = 10,以为d[n]会被更新成最小的值。
我们取出(10,5)的时候,d[5] = 10, 我们通过 d[5] = 10 去更新别的点, 更新完了之后, 假设我们接下来取出的是 (100,5) d[5] != 100, 说明5号点已经通过最优的距离更新过其他点了, 就不需要再更新一次了,从而达到标记的效果。
现在代码的复杂度就是 n*lg(n) + 2*E了。就可以解决一开始的问题了。
关于多源点最短路的问题。
现在有n个地点,m条双向边,现在有p个商店,小明想知道从任意一个点出发,到附近最近的商店的距离至少是多少。 1 <= n <= 1e5 1 <= m <= 1e6
这个问题咋一看,需要求出每个点到附近的商店的最短路是多少,没有任何头绪,然后我们转化思路,求出所有商店到任意一点的最短路。也还是多个点到多个点的最短路。
难道跑p遍dijkstra 还是跑一遍flody?
都不对,其实我们可以发现,从第1个商店走到x点的和第2个商店走到x点的距离,他的性质是不发生变化的,也就是说,都是某一个商店到x的距离。
我们转化一下思路, 开一个假的节点 s, 然后s和每个商店都存在着一条距离为0的边,我们再以s为起点,跑出s点到任意点的最短路,那么我们就可以通过一遍dijkstra得到每个点到s的最短路是多少,也是任意一点到商店的最短路径是多少,就解出来了。
题意:就是有n个城市,m条单向边,现在有p个特殊城市,求这p个特殊城市中 两两之间的最小距离是多少。
题解:这个题目的思路和上面是一样的,就是我们走路的时候带一个从特殊城市出发的标记,每次往前走的时候,遇到相同的标记就不再走,遇到不同的标记,就更新一下答案。最后找到答案然后输出就好了。
代码:
#include<bits/stdc++.h>
using namespace std;
#define Fopen freopen("_in.txt","r",stdin); freopen("_out.txt","w",stdout);
#define LL long long
#define ULL unsigned LL
#define fi first
#define se second
#define pb push_back
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define lch(x) tr[x].son[0]
#define rch(x) tr[x].son[1]
#define max3(a,b,c) max(a,max(b,c))
#define min3(a,b,c) min(a,min(b,c))
typedef pair<int,int> pll;
const int inf = 0x3f3f3f3f;
const LL INF = 0x3f3f3f3f3f3f3f3f;
const LL mod = (int)1e9+;
const int N = 4e5 + ;
struct Node{
int fa;
LL dis;
int o;
bool operator < (const Node & x) const{
return dis > x.dis;
}
};
LL dis[N], ans[N], vis[N];
int a[N];
int head[N], nt[N], to[N]; LL d[N];
int tot;
void add(int u, int v, LL val){
to[tot] = v;
d[tot] = val;
nt[tot] = head[u];
head[u] = tot++;
}
priority_queue<Node> q;
int n, m, p;
void dijk(){
for(int i = ; i <= p; i++){
q.push({a[i],,a[i]});
dis[a[i]] = ;
vis[a[i]] = a[i];
}
while(!q.empty()){
LL dd = q.top().dis; int rt = q.top().fa, u = q.top().o;
q.pop();
if(dis[u] < dd) continue;
vis[u] = rt;
for(int i = head[u]; ~i; i = nt[i]){
int v = to[i];
LL w = d[i];
if(vis[v] == vis[u]) continue;
if(vis[v]) {
LL tmp = dis[v] + dd + w;
ans[rt] = min(ans[rt], tmp);
ans[vis[v]] = min(ans[vis[v]], tmp);
}
if(w + dis[u] < dis[v]){
dis[v] = dis[u] + w;
q.push({rt, dis[v], v});
}
}
}
}
void init(){
memset(dis, INF, sizeof(dis));
memset(head, -, sizeof(head));
memset(ans, INF, sizeof(ans));
memset(vis, , sizeof(vis));
tot = ;
}
int main(){
int T;
scanf("%d", &T);
for(int cas = ; cas <= T; cas++){
scanf("%d%d", &n, &m);
int u, v, w;
init();
for(int i = ; i <= m; i++){
scanf("%d%d%d", &u, &v, &w);
add(u, v, w);
}
scanf("%d", &p);
for(int i = ; i <= p; i++)
scanf("%d", &a[i]);
dijk();
LL Ans = INF;
for(int i = ; i <= p; i++)
Ans = min(Ans, ans[a[i]]);
printf("Case #%d: %lld\n", cas, Ans);
} return ;
}
关于dijkstra的优化 及 多源最短路的更多相关文章
- 最短路模板(Dijkstra & Dijkstra算法+堆优化 & bellman_ford & 单源最短路SPFA)
关于几个的区别和联系:http://www.cnblogs.com/zswbky/p/5432353.html d.每组的第一行是三个整数T,S和D,表示有T条路,和草儿家相邻的城市的有S个(草儿家到 ...
- hdu 2544 单源最短路问题 dijkstra+堆优化模板
最短路 Time Limit: 5000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Submis ...
- POJ 2502 - Subway Dijkstra堆优化试水
做这道题的动机就是想练习一下堆的应用,顺便补一下好久没看的图论算法. Dijkstra算法概述 //从0出发的单源最短路 dis[][] = {INF} ReadMap(dis); for i = 0 ...
- [ACM_图论] Domino Effect (POJ1135 Dijkstra算法 SSSP 单源最短路算法 中等 模板)
Description Did you know that you can use domino bones for other things besides playing Dominoes? Ta ...
- 最短路算法模板合集(Dijkstar,Dijkstar(优先队列优化), 多源最短路Floyd)
再开始前我们先普及一下简单的图论知识 图的保存: 1.邻接矩阵. G[maxn][maxn]; 2.邻接表 邻接表我们有两种方式 (1)vector< Node > G[maxn]; 这个 ...
- POJ-2387.Til the Cows Come Home.(五种方法:Dijkstra + Dijkstra堆优化 + Bellman-Ford + SPFA + Floyd-Warshall)
昨天刚学习完最短路的算法,今天开始练题发现我是真的菜呀,居然能忘记邻接表是怎么写的,真的是菜的真实...... 为了弥补自己的菜,我决定这道题我就要用五种办法写出,并在Dijkstra算法堆优化中另外 ...
- 单源最短路模板(dijkstra)
单源最短路(dijkstra算法及堆优化) 弱化版题目链接 n^2 dijkstra模板 #include<iostream> #include<cstdio> #includ ...
- Dijkstra堆优化
Dijkstra是一个非常不错的最短路算法,它使用两层循环进行枚举,通过每次更新蓝白点的方式更新最短路,时间复杂度为O(n^2),优于floyd的O(n^3),不过只能用于计算单源最短路,而且无法处理 ...
- Dijkstra堆优化+邻接表
Dijkstra算法是个不错的算法,但是在优化前时间复杂度太高了,为O(nm). 在经过堆优化后(具体实现用的c++ STL的priority_queue),时间复杂度为O((m+n) log n), ...
随机推荐
- codeforces 576 div2 A-D题解
A题 Description 题目链接: https://codeforces.com/contest/1199/problem/A 题意: 给定长度为n(1≤n≤100000)的一个序列a,以及两个 ...
- Go组件学习——gorm四步带你搞定DB增删改查
1.简介 ORM Object-Relationl Mapping, 它的作用是映射数据库和对象之间的关系,方便我们在实现数据库操作的时候不用去写复杂的sql语句,把对数据库的操作上升到对于对象的操作 ...
- 跟着大彬读源码 - Redis 9 - 对象编码之 三种list
目录 1 ziplist 2 skiplist 3 quicklist 总结 Redis 底层使用了 ziplist.skiplist 和 quicklist 三种 list 结构来实现相关对象.顾名 ...
- 《SpringCloud docker》读书笔记
yml配置意义 当Ribbon和Eureka配合使用时,会自动将虚拟主机名映射成微服务的网络地址. yml中info可以展示一些信息 server: port: 8000 # 指定端口 spring: ...
- Opengl_入门学习分享和记录_03_渲染管线(二)再谈顶点着色器以及顶点属性以及属性链接
---恢复内容开始--- 写在前面的废话:岂可修!感觉最近好忙啊,本来今天还有同学约我出去玩的.(小声bb) 正文开始:之前已经编译好的着色器中还有一些问题,比如 layout(location=0) ...
- Codeforces 436D Pudding Monsters
题意简述 开始有无限长的一段格子,有n个格子种有布丁怪兽,一开始连续的布丁怪兽算一个布丁怪兽. 每回合你可以将一个布丁怪兽向左或右移动,他会在碰到第一个布丁怪兽时停下,并与其合并. 有m个特殊格子,询 ...
- 《深入理解Java虚拟机》- Java虚拟机是如何加载Java类的?
Java虚拟机是如何加载Java类的? 这个问题也就是面试常问到的Java类加载机制.在年初面试百战之后,菜鸟喜鹊也是能把这流程倒背如流啊!但是,也只是字面上的背诵,根本就是像上学时背书考试一样. ...
- ubuntu16.04双系统创建分区
ubuntu安装分区 安装ubuntu 图1:Ubuntu Linux分区向导 如果希望对分区过程进行完全控制,可以使用"其它"选项.单击"继续"按钮,安装向导 ...
- Day 02--选题与设计(二)
1.今天我们主要设计了一下我们微信小程序可以实现的功能,客户操作的基本流程,研究了墨刀这个工具的使用方法并试着将想法转化为原型设计项目.我们给自己的系统起名为“天天好餐”.我们认为食堂订送餐与网络上的 ...
- 深入浅出TCP与UDP协议
深入浅出TCP与UDP协议 网络协议是每个前端工程师的必修课,TCP/IP协议族是一系列网络协议的总和,而其中两个具有代表性的传输层协议,分别是TCP与UDP,本文将介绍这两者以及他们之间的区别. 一 ...