先来看这样一道题目

给你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. XSS危害——session劫持(转载)

    在跨站脚本攻击XSS中简单介绍了XSS的原理及一个利用XSS盗取存在cookie中用户名和密码的小例子,有些同学看了后会说这有什么大不了的,哪里有人会明文往cookie里存用户名和密码.今天我们就介绍 ...

  2. “$Bitmap 有标记已使用的未用簇”

    前几天在电脑上用 DiskGenius 给移动硬盘分区的时候出现了这个错误,如下图所示: 解决方法: 在 cmd 命令行窗口中输入如下代码: chkdsk /f /x c: PS: 其中 " ...

  3. poj 2503 Babelfish(字典树或map或哈希或排序二分)

    输入若干组对应关系,然后输入应该单词,输出对应的单词,如果没有对应的输出eh 此题的做法非常多,很多人用了字典树,还有有用hash的,也有用了排序加二分的(感觉这种方法时间效率最差了),这里我参考了M ...

  4. JDBC连接池-C3P0连接

    JDBC连接池-C3P0连接 c3p0连接池的学习英语好的看英文原版      c3p0 - JDBC3 Connection and Statement Pooling 使用c3p0连接池  三种方 ...

  5. JS 自执行函数

    由于自己js基础知识薄弱,很多js的知识还没有掌握,所以接下来会经常写一些关于js基础知识的博客,也算给自己提个醒吧. js自执行函数,听到这个名字,首先会联想到函数.接下来,我来定义一个函数: fu ...

  6. 【原创】原来你竟然是这样的Chrome?!Firefox笑而不语

    书接上文 上一篇文章<[原创]用事实说话,Firefox 的性能是 Chrome 的 2 倍,Edge 的 4 倍,IE11 的 6 倍!>,我们对比了不同浏览器下FineUIPro一个页 ...

  7. Kafka消息队列初识

    一.Kafka简介 1.1 什么是kafka kafka是一个分布式.高吞吐量.高扩展性的消息队列系统.kafka最初是由Linkedin公司开发的,后来在2010年贡献给了Apache基金会,成为了 ...

  8. 【Java例题】2.5 温度转换

    5.输入华氏温度, 用下列公式将其转换为摄氏温度并输出. C=5/9(F-32). package study; import java.util.Scanner; public class demo ...

  9. 安装CUDA9.0及对应版本的tensorflow-gpu详细过程(Windows server 2012R2版本也可以)

    由于最近跑机器学习相关代码的时候CPU运算速度跟不上,这才利用GPU来运算代码,显然使用GPU来运算速度明显要快很多,但是搭配GPU的使用环境是真的麻烦且头疼.网上有很多牛人的搭建过程,虽然他们都成功 ...

  10. mysql docker 主从配置

    主从复制相关 前置条件: docker安装的mysql是5.7.26版本 1. 编排docker-compose文件如下: version: '3' services: mysql-master: v ...