先来看这样一道题目

给你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的最短路是多少,也是任意一点到商店的最短路径是多少,就解出来了。

HDU - 6166

题意:就是有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的优化 及 多源最短路的更多相关文章

  1. 最短路模板(Dijkstra & Dijkstra算法+堆优化 & bellman_ford & 单源最短路SPFA)

    关于几个的区别和联系:http://www.cnblogs.com/zswbky/p/5432353.html d.每组的第一行是三个整数T,S和D,表示有T条路,和草儿家相邻的城市的有S个(草儿家到 ...

  2. hdu 2544 单源最短路问题 dijkstra+堆优化模板

    最短路 Time Limit: 5000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submis ...

  3. POJ 2502 - Subway Dijkstra堆优化试水

    做这道题的动机就是想练习一下堆的应用,顺便补一下好久没看的图论算法. Dijkstra算法概述 //从0出发的单源最短路 dis[][] = {INF} ReadMap(dis); for i = 0 ...

  4. [ACM_图论] Domino Effect (POJ1135 Dijkstra算法 SSSP 单源最短路算法 中等 模板)

    Description Did you know that you can use domino bones for other things besides playing Dominoes? Ta ...

  5. 最短路算法模板合集(Dijkstar,Dijkstar(优先队列优化), 多源最短路Floyd)

    再开始前我们先普及一下简单的图论知识 图的保存: 1.邻接矩阵. G[maxn][maxn]; 2.邻接表 邻接表我们有两种方式 (1)vector< Node > G[maxn]; 这个 ...

  6. POJ-2387.Til the Cows Come Home.(五种方法:Dijkstra + Dijkstra堆优化 + Bellman-Ford + SPFA + Floyd-Warshall)

    昨天刚学习完最短路的算法,今天开始练题发现我是真的菜呀,居然能忘记邻接表是怎么写的,真的是菜的真实...... 为了弥补自己的菜,我决定这道题我就要用五种办法写出,并在Dijkstra算法堆优化中另外 ...

  7. 单源最短路模板(dijkstra)

    单源最短路(dijkstra算法及堆优化) 弱化版题目链接 n^2 dijkstra模板 #include<iostream> #include<cstdio> #includ ...

  8. Dijkstra堆优化

    Dijkstra是一个非常不错的最短路算法,它使用两层循环进行枚举,通过每次更新蓝白点的方式更新最短路,时间复杂度为O(n^2),优于floyd的O(n^3),不过只能用于计算单源最短路,而且无法处理 ...

  9. Dijkstra堆优化+邻接表

    Dijkstra算法是个不错的算法,但是在优化前时间复杂度太高了,为O(nm). 在经过堆优化后(具体实现用的c++ STL的priority_queue),时间复杂度为O((m+n) log n), ...

随机推荐

  1. codeforces 576 div2 A-D题解

    A题 Description 题目链接: https://codeforces.com/contest/1199/problem/A 题意: 给定长度为n(1≤n≤100000)的一个序列a,以及两个 ...

  2. Go组件学习——gorm四步带你搞定DB增删改查

    1.简介 ORM Object-Relationl Mapping, 它的作用是映射数据库和对象之间的关系,方便我们在实现数据库操作的时候不用去写复杂的sql语句,把对数据库的操作上升到对于对象的操作 ...

  3. 跟着大彬读源码 - Redis 9 - 对象编码之 三种list

    目录 1 ziplist 2 skiplist 3 quicklist 总结 Redis 底层使用了 ziplist.skiplist 和 quicklist 三种 list 结构来实现相关对象.顾名 ...

  4. 《SpringCloud docker》读书笔记

    yml配置意义 当Ribbon和Eureka配合使用时,会自动将虚拟主机名映射成微服务的网络地址. yml中info可以展示一些信息 server: port: 8000 # 指定端口 spring: ...

  5. Opengl_入门学习分享和记录_03_渲染管线(二)再谈顶点着色器以及顶点属性以及属性链接

    ---恢复内容开始--- 写在前面的废话:岂可修!感觉最近好忙啊,本来今天还有同学约我出去玩的.(小声bb) 正文开始:之前已经编译好的着色器中还有一些问题,比如 layout(location=0) ...

  6. Codeforces 436D Pudding Monsters

    题意简述 开始有无限长的一段格子,有n个格子种有布丁怪兽,一开始连续的布丁怪兽算一个布丁怪兽. 每回合你可以将一个布丁怪兽向左或右移动,他会在碰到第一个布丁怪兽时停下,并与其合并. 有m个特殊格子,询 ...

  7. 《深入理解Java虚拟机》- Java虚拟机是如何加载Java类的?

    Java虚拟机是如何加载Java类的?  这个问题也就是面试常问到的Java类加载机制.在年初面试百战之后,菜鸟喜鹊也是能把这流程倒背如流啊!但是,也只是字面上的背诵,根本就是像上学时背书考试一样. ...

  8. ubuntu16.04双系统创建分区

    ubuntu安装分区 安装ubuntu 图1:Ubuntu Linux分区向导 如果希望对分区过程进行完全控制,可以使用"其它"选项.单击"继续"按钮,安装向导 ...

  9. Day 02--选题与设计(二)

    1.今天我们主要设计了一下我们微信小程序可以实现的功能,客户操作的基本流程,研究了墨刀这个工具的使用方法并试着将想法转化为原型设计项目.我们给自己的系统起名为“天天好餐”.我们认为食堂订送餐与网络上的 ...

  10. 深入浅出TCP与UDP协议

    深入浅出TCP与UDP协议 网络协议是每个前端工程师的必修课,TCP/IP协议族是一系列网络协议的总和,而其中两个具有代表性的传输层协议,分别是TCP与UDP,本文将介绍这两者以及他们之间的区别. 一 ...