先来看这样一道题目

给你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. 通过mark和reset方法重复利用InputStream

    InputStreammarkreset 在这篇博客中我们已经简单的知道可以通过缓存InputStream来重复利用一个InputStream,但是这种方式的缺点也是明显的,就是要缓存一整个Input ...

  2. 统计学习方法6—logistic回归和最大熵模型

    目录 logistic回归和最大熵模型 1. logistic回归模型 1.1 logistic分布 1.2 二项logistic回归模型 1.3 模型参数估计 2. 最大熵模型 2.1 最大熵原理 ...

  3. codeforces 322 B Ciel and Flowers

    题目链接 有红绿蓝三种颜色的画,每种拿三朵可以组成一束花,或者各拿一朵组成花束,告诉你每种花的数目,求出可能组成最多的花束. 如果你的代码过不了,考虑一下 8 8 9这种组合.  因为数据量很大,我的 ...

  4. Svn提交冲突问题

    MEclipse中的svn冲突解决办法: 1.        点击提交,报错——‘SVN提交’has encountered a problem. 2.        选中无法提交的文件,点击更新操作 ...

  5. 后台post注入爆密码

    后台登陆框post注入按照注入的方式属于post,和前台搜索型post注入.文本框注入类似,由于目前主流的注 入工具除了穿山甲等较新工具以外几乎都是get注入,尤其是对于这种后台账户型post注入式无 ...

  6. Qt基于sqlite数据库的管理小软件

    闲来无事,写了一个基于sqlite的数据库管理小软件. 先上图 中心思想就是: 创建一个数据库 然后每一个分组对应一个数据表 然后遍历该数据表.将名字以treewidgetItem显示出来.添加删除实 ...

  7. 什么是Singleton?

    Singleton:在Java中即指单例设计模式,它是软件开发中最常用的设计模式之一. 单:指唯一 例:指实例 单例设计模式,即某个类在整个系统中只能有一个实例对象可被获取和使用的代码模式. 要点: ...

  8. 基于hprose-golang创建RPC微服务

    Hprose(High Performance Remote Object Service Engine) 是一款先进的轻量级.跨语言.跨平台.无侵入式.高性能动态远程对象调用引擎库.它不仅简单易用, ...

  9. idea+Spring+Mybatis+jersey+jetty构建一个简单的web项目

    一.先使用idea创建一个maven项目. 二.引入jar包,修改pom.xml <dependencies> <dependency> <groupId>org. ...

  10. SonarQube部署及代码质量扫描入门教程

    一.前言 1.本文主要内容 CentOS7下SonarQube部署 Maven扫描Java项目并将扫描结果提交到SonarQube Server SonarQube扫描报表介绍 2.环境信息 工具/环 ...