常用最短路优化算法及例题(附模板)——-SPFA和Dijkstra

常用最短路算法——-SPFA和Dijkstra及其优化

这篇文章将简单讲解两个最常用的最短路优化算法,需要读者有一定的图论基础。

首先从DIJKSTRA讲起。常规的dijkstra算法复杂度较高,为O(n^2),因为要花大量时间来找当前已知的距顶点距离最小的值,所以用优先队列(值小的先出队列)来优化,可大大优化时间复杂度。STL中优先队列的操作单次复杂度为O(logN),所以经过优先队列优化的dijkstra时间复杂度会降到O(N*logN);

以下为核心部分代码:

 1 struct pack{int s,dist;};//利用一个结构体存储节点的起点与到达它的最小距离
 2 bool operator<(const pack &a,const pack&b){
 3     return a.dist>b.dist;
 4 }//重载pack的小于号,使得大根堆的优先队列为小根堆
 5 priority_queue<pack> q;
 6 void dij(){
 7     memset(dist,0x7f,sizeof(dist));//初始化最大值
 8     memset(vis,0,sizeof(vis));
 9     int s=1;
10     vis[s]=1;
11     dist[s]=0;
12     q.push((pack){s,dist[s]});//将起点放入
13     while(!q.empty()){
14         pack t=q.top();q.pop();//取出距离最小的点
15         int from=t.s;
16         if(vis[from])continue;
17         vis[from]=1;
18         for(int i=first[from];i;i=edge[i].next){
19                         int to=edge[i].to
20             if(dist[to]>edge[i].val+t.dist){
21                 dist[to]=edge[i].val+t.dist;
22                 q.push((pack){to,dist[to]});//松弛操作
23             }
24                 }
25     }
26 }

Dijkstra

很多时候,给定的图存在负权边,这时类似Dijkstra算法等便没有了用武之地,而Bellman-Ford算法的复杂度又过高,SPFA算法便派上用场了。简洁起见,我们约定加权有向图G不存在负权回路,即最短路径一定存在。如果某个点进入队列的次数超过N次则存在负环(SPFA无法处理带负环的图)。

我们用数组dist记录每个结点的最短路径估计值,而且用邻接表来存储图G。我们采取的方法是动态逼近法:设立一个先进先出的队列用来保存待优化的结点,优化时每次取出队首结点from,并且用from点当前的最短路径估计值对离开from点所指向的结点to进行松弛操作,如果to点的最短路径估计值有所调整,且to点不在当前的队列中,就将to点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止。

期望时间复杂度:O(mE), 其中m为所有顶点进队的平均次数,可以证明m一般小于等于2n;(除非数据点专门卡SPFA,通常m都不大)

以下是队列实现的SPFA模板

queue<int> q;
void spfa(int s){
    memset(vis,0,sizeof(vis));
    memset(dist,0x7f,sizeof(dist));
    vis[s]=1;
    dis[s]=0;
    q.push(start);
    while(q.size()){
        int from=q.front();
        for(int i=first[from];i;i=edge[i].next){
            int to=edge[i].to;
            if(dist[from]+edge[i].val<dist[to]){
                dist[to]=dist[from]+edge[i].val;
                if(!vis[to]){
                    q.push(to);
                    vis[to]=1;
                }
            }
        }
        vis[from]=0;
        q.pop();
    }
}

SPFA

SPFA算法有两个优化算法 SLF 和 LLL: SLF:Small Label First 策略,设要加入的节点是j,队首元素为i,若dist(j)<dist(i),则将j插入队首,否则插入队尾。 LLL:Large Label Last 策略,设队首元素为i,队列中所有dist值的平均值为x,若dist(i)>x则将i插入到队尾,查找下一元素,直到找到某一i使得dist(i)<=x,则将i出对进行松弛操作。 SLF 可使速度提高 15 ~ 20%;SLF + LLL 可提高约 50%。 在实际的应用中SPFA的算法时间效率不是很稳定,为了避免最坏情况的出现,通常使用效率更加稳定的Dijkstra算法。(经过优化的DIJ复杂度稳定在O(N*logN)左右)

下面附利用STL中的双端队列优化的SLF优化代码:

int spfa(int start){
    memset(vis,0,sizeof(vis));
    memset(dist,0x7f,sizeof(dist));
    deque<int> q;//STL中的双端队列 可在两端实现入队出队
    vis[start]=1;
    dist[start]=0;
    num[start]++;
    q.push_back(start);
    while(q.size()){
        int from= q.front();q.pop_front();//每次从前端取出
        vis[from]=0;
        for(int i=first[from];i;i=edge[i].next){
            int to=edge[i].to;
            if(dist[from]+edge[i].val<dist[to]){
                dist[to]=dist[from]+edge[i].val;
                if(!vis[to]){
                    vis[to]=1;
                    num[to]++;
                    if(num[to]==n)return 0;//判断是否有负环存在
                    if(!q.empty()){
                        if(dist[to]>dist[q.front()])//距离大的往后端放
                            q.push_back(to);
                        else q.push_front(to);//小的放前端
                        }
                    else q.push_back(to);
                }
            }
        }
    }
    return 1;
}

SPFA SLF

例题 CodeVS 1021 玛丽卡

题目描述 Description

麦克找了个新女朋友,玛丽卡对他非常恼火并伺机报复。

因为她和他们不住在同一个城市,因此她开始准备她的长途旅行。

在这个国家中每两个城市之间最多只有一条路相通,并且我们知道从一个城市到另一个城市路上所需花费的时间。

麦克在车中无意中听到有一条路正在维修,并且那儿正堵车,但没听清楚到底是哪一条路。无论哪一条路正在维修,从玛丽卡所在的城市都能到达麦克所在的城市。

玛丽卡将只从不堵车的路上通过,并且她将按最短路线行车。麦克希望知道在最糟糕的情况下玛丽卡到达他所在的城市需要多长时间,这样他就能保证他的女朋友离开该城市足够远。

编写程序,帮助麦克找出玛丽卡按最短路线通过不堵车道路到达他所在城市所需的最长时间(用分钟表示)。

输入描述 Input Description

第一行有两个用空格隔开的数N和M,分别表示城市的数量以及城市间道路的数量。1≤N≤1000,1≤M≤N*(N-1)/2。城市用数字1至N标识,麦克在城市1中,玛丽卡在城市N中。

接下来的M行中每行包含三个用空格隔开的数A,B和V。其中1≤A,B≤N,1≤V≤1000。这些数字表示在A和城市B中间有一条双行道,并且在V分钟内是就能通过。

输出描述 Output Description

输出文件的第一行中写出用分钟表示的最长时间,在这段时间中,无论哪条路在堵车,玛丽卡应该能够到达麦克处,如果少于这个时间的话,则必定存在一条路,该条路一旦堵车,玛丽卡就不能够赶到麦克处。

样例输入 Sample Input

5 7

1 2 8

1 4 10

2 3 9

2 4 10

2 5 1

3 4 7

3 5 10

样例输出 Sample Output

27

先DIJ记录最短路径的边 然后枚举去掉每一条最短路径的边,DIJ最后取最大值

当然,可以将DIJ换成SPFA,也是同样能AC的

DIJ AC:

#include<bits/stdc++.h>
using namespace std;
int n,m,cnt,knt;
int vis[1010],dist[1010],first[1010],next[600000];
bool ask[1010][1010];
int read(int &x){          //读入优化
    char c=getchar();x=0;
    while(c<‘0‘||c>‘9‘)c=getchar();
    while(c>=‘0‘&&c<=‘9‘)x=(x<<3)+(x<<1)+c-‘0‘,c=getchar();
    return x;
}
struct EDGE{
    int from,to,val,next;
}edge[600000];
struct pack{
    int s,dist;
};
bool operator<(const pack &a,const pack &b){
  return a.dist>b.dist;
}
void addedge(int a,int b,int c){
    edge[++cnt].next=first[a];
    edge[cnt].from=a;
    edge[cnt].to=b;
    edge[cnt].val=c;
    first[a]=cnt;
}
void ini(){
    read(n);read(m);
    int x,y,z;
    for(int i=1;i<=m;i++){
        read(x);read(y);read(z);
        addedge(x,y,z);
        addedge(y,x,z);
    }
}
void dijclear(){
    memset(dist,0x7f,sizeof(dist));
    memset(vis,0,sizeof(vis));
}
int dij(int s){
    dijclear();
    priority_queue<pack> q;
    q.push((pack){s,dist[s]=0});
    while(!q.empty()){
        pack temp=q.top();q.pop();
        int from=temp.s;
        if(vis[from])continue;
        vis[from]=1;
        int k;
        for(int i=first[from];i;i=edge[i].next){
            if(temp.dist+edge[i].val<dist[edge[i].to]){
                dist[edge[i].to]=temp.dist+edge[i].val;
                next[edge[i].to]=from;
                q.push((pack){edge[i].to,dist[edge[i].to]});
            }
        }
    }
    return dist[1];
}
int dij2(int s){
    dijclear();
    priority_queue<pack> q;
    q.push((pack){s,dist[s]=0});
    while(!q.empty()){
        pack temp=q.top();q.pop();
        int from=temp.s;
        if(vis[from])continue;
        vis[from]=1;
        int k;
        for(int i=first[from];i;i=edge[i].next){
            if(temp.dist+edge[i].val<dist[edge[i].to]&&!ask[from][edge[i].to]){
                dist[edge[i].to]=temp.dist+edge[i].val;
                q.push((pack){edge[i].to,dist[edge[i].to]});
            }
        }
    }
    return dist[1];
}
int main(){
    ini();
    int ans=dij(n);
    for(int i=1;i!=n;i=next[i]){
        ask[next[i]][i]=1;
        ans=max(dij2(n),ans);//将每条边遮住一遍多次求最短路
        ask[next[i]][i]=0;
    }
    printf("%d",ans);
    /*int x=1;
    while(x!=0){
        printf("%d->",x);
        x=next[x];
    } */
    return 0;
}
时间: 2024-11-07 20:48:10

常用最短路优化算法及例题(附模板)——-SPFA和Dijkstra的相关文章

机器学习最常用优化之一——梯度下降优化算法综述

转自:http://www.dataguru.cn/article-10174-1.html 梯度下降算法是机器学习中使用非常广泛的优化算法,也是众多机器学习算法中最常用的优化方法.几乎当前每一个先进的(state-of-the-art)机器学习库或者深度学习库都会包括梯度下降算法的不同变种实现.但是,它们就像一个黑盒优化器,很难得到它们优缺点的实际解释.这篇文章旨在提供梯度下降算法中的不同变种的介绍,帮助使用者根据具体需要进行使用. 这篇文章首先介绍梯度下降算法的三种框架,然后介绍它们所存在的

【优化算法】Greedy Randomized Adaptive Search算法 超详细解析,附代码实现TSP问题求解

01 概述 Greedy Randomized Adaptive Search,贪婪随机自适应搜索(GRAS),是组合优化问题中的多起点元启发式算法,在算法的每次迭代中,主要由两个阶段组成:构造(construction)和局部搜索( local search). 构造(construction)阶段主要用于生成一个可行解,而后该初始可行解会被放进局部搜索进行邻域搜索,直到找到一个局部最优解为止. 02 整体框架 如上面所说,其实整一个算法的框架相对于其他算法来说还算比较简单明了,大家可以先看以

acm常见算法及例题

转自:http://blog.csdn.net/hengjie2009/article/details/7540135 acm常见算法及例题 初期:一.基本算法:     (1)枚举. (poj1753,poj2965)     (2)贪心(poj1328,poj2109,poj2586)     (3)递归和分治法.     (4)递推.     (5)构造法.(poj3295)     (6)模拟法.(poj1068,poj2632,poj1573,poj2993,poj2996)二.图算法

深度解读最流行的优化算法:梯度下降

深度解读最流行的优化算法:梯度下降 By 机器之心2016年11月21日 15:08 梯度下降法,是当今最流行的优化(optimization)算法,亦是至今最常用的优化神经网络的方法.本文旨在让你对不同的优化梯度下降法的算法有一个直观认识,以帮助你使用这些算法.我们首先会考察梯度下降法的各种变体,然后会简要地总结在训练(神经网络或是机器学习算法)的过程中可能遇到的挑战.(本文的中文版 PDF 下载地址) 目录: 梯度下降的各种变体 批量梯度下降(Batch gradient descent)

梯度下降优化算法综述

本文翻译自Sebastian Ruder的"An overview of gradient descent optimization algoritms",作者首先在其博客中发表了这篇文章,其博客地址为:An overview of gradient descent optimization algoritms,之后,作者将其整理完放在了arxiv中,其地址为:An overview of gradient descent optimization algoritms,在翻译的过程中以

梯度优化算法总结(转载)以及solver中相关参数解释

原文地址:http://sebastianruder.com/optimizing-gradient-descent/ 如果熟悉英文的话,强烈推荐阅读原文,毕竟翻译过程中因为个人理解有限,可能会有谬误,还望读者能不吝指出.另外,由于原文太长,分了两部分翻译,本篇主要是梯度下降优化算法的总结,下篇将会是随机梯度的并行和分布式,以及优化策略的总结. 梯度下降是优化中最流行的算法之一,也是目前用于优化神经网络最常用到的方法.同时,每个优秀的深度学习库都包含了优化梯度下降的多种算法的实现(比如, las

使用Golang编写优化算法 (1)

动手写点东西是学习新知识很重要的一个阶段.之前用 Python 和 JavaScript 实现优化算法,现在用 Golang 来实现.语法上略有不爽,某些C语言的思维又回来了. - Golang 用 package 来组织代码,同一 package 下不同文件之间的标识符是共享的,不能包含两个相同名称的函数.而且只有 package main 能够包含 main 函数.所以将公用的函数提取出来,放在package common.同时,每种例子程序移动到 examples 目录下. - 在 Cle

游戏制作中的大宝剑---常用的数据结构与算法

前言 时间流逝,物是人非,就好像涌动的河流,永无终焉,幼稚的心智将变得高尚,青年的爱慕将变得深刻,清澈之水折射着成长. ----------<塞尔塔传说> PS:为了方便大家阅读,个人认为比较重要的内容-------红色字体显示 个人认为可以了解的内容-------紫色字体显示 --------------------------------------------------------------------------- ---------------------------------

常用的SQL分页算法及对比

SQL Server 2005引入的新方法. 1 SELECT * FROM (SELECT ROW_NUMBER() OVER(ORDER BY keyField DESC) AS rowNum, * FROM tableName) AS t WHERE rowNum > start[比如:90] AND rowNum <= end[比如:100]=>[返回91-100] 2 3 SELECT top (PAGESIZE[比如:10]) FROM (SELECT ROW_NUMBER(