Dijkstra以及heap优化详解

博客食用更佳bossbaby's blog

迪杰斯特拉算法是由荷兰计算机科学家狄克斯特拉于1959 年提出的,因此又叫狄克斯特拉算法。是从一个顶点到其余各顶点的最短路径算法,解决的是有向图中最短路径问题。迪杰斯特拉算法主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。

引自 百度百科

要求

迪杰斯特拉算法的要求是不能存在负权边(而\(SPFA\)可以),可存在环(而\(SPFA\)则不行)

过程

迪杰斯特拉会从出发点开始枚举每个可到达的点,用这个点去松弛这个点未到达过的可到达的点

1.什么是松弛呢

假设现在有点\(u\),\(v\)(正在搜索u,还未搜索过\(v\)).\(u\)\(v\)的距离为\(d[u][v]\).目前出发点到\(u\),\(v\)的距离分别是\(dis[u]\),\(dis[v]\).那么如果\(dis[u]+d[u][v]<dis[v]\)(\(dis[v]\)被初始化为\(d[start][v]\)(\(start\)为原点)或\(INF\),\(INF\)通常定义为\(INT\_MAX\))那么就把\(dis[v]\)更新为\(dis[u]+d[u][v]\),并继续搜索.

2.具体搜索方法

我们会先枚举离出发点最近(可能是更新后的),也是\(dis\)值最小,且未访问过的一个节点.然后,进行松弛他的所有可以连接的节点.最后所有点的\(dis\)值就是他到\(start\)(原点)的最短距离.

3.为什么松弛的不仅是未访问过的节点

假设现在正在搜索\(u\),有一个访问过的节点\(v\)可以连通到\(u\).首先,\(dis[v]\)是可能会大于\(dis[u]\)的,应为第一次搜索进入\(v\)的边的权值可能非常大.所以再更新一遍比较稳妥.

4.为什么图不能有负权边

感性理解一下就会发现不行

这样就会改变应有的搜索顺序,使得一些本应该先放问的点后面再访问,而一些次优点被先访问.

这个算法其实有贪心的成分在里面,因为是最短,所以每次都从最短的开始搜索.

而负权边会使得路径一直减小.

5.代码

由于这不是最优的解决方案,可以优化,所以直接写的不是特别详细,可以看百度百科

邻接矩阵

#include<bits/stdc++.h>
#define MAXN 100000
using namespace std;
int d[MAXN][MAXN],dis[MAXN];//d为邻接矩阵,dis为每个点到原点的最小距离,d不连通为INF(=INT_MAX或memset为0x3f)
bool vis[MAXN];//vis标记访问过的节点
int n;//点个数
//省去输入输出
void dij(int s/*出发点*/){
    memset(vis,0,sizeof(vis));//初始化
    vis[s]=1;//原点标记已经搜索过,搜索在下一行
    for(int  i=1;i<=n;i++)dis[i]=d[s][i];//搜索原点
    dis[s]=0;//原点到原点距离为0
    for(int i=1;i<n;i++){//注意,进行n-1次
        int mindis=MAX_INT;//当前未搜索过的最小节点的离原点的距离
        int current;//当前未搜索过的最小节点
        for(int j=1;j<=n;j++){
            if(!vis[j]&&mindis>dis[j]){
                mindis=dis[j];
                current=j;//更新
            }
        }
        vis[current]=1;//标记已经搜索过
        for(int j=1;j<=n;j++){
            dis[j]=min(dis[current]+d[current][s],dis[j]);
        }
    }
}
//完成

邻接表

//参见上面自行修改...能学dij的应该这个都会吧...

优化

大家想一想,每次都寻找最小的,是不是有点浪费时间,不如每次用堆(\(heap\))维护起来,每次更新一个点后就把它扔进堆里面,每次取最上面的.而假如这个点已经又被更新过了一次,就跳过.如何判断呢?每次把这个点的编号和\(dis\)值扔进去,取出时如果遇见丢进取得\(dis\)与现在的\(dis\)不同,就\(continue\),因为在更新时这个点又被扔进过堆里了.所以不用担心漏过.这样还有一个好处,就是不用存储\(vis\),即是否访问过,所有的都在堆里面了.

堆怎么写

别问我QWQ,我也不会,直接用\(STL\)\(priority\_queue\)

代码

既然没有什么问题,就上代码了.

//使用的是c++11
//复杂度 O(nlogn)
//比较与SPFA O(m/*边*/)
//更适合稠密图
#include<bits/stdc++.h>
#define INF LLONG_MAX
using namespace std;
using pii=pair<int,long long>;
using ll=long long;
int n;
ll d[1010][1010];
ll dis[1010];
int cnt[1010];//每个点连接的边的个数
pii e[1010][1010];//邻接表

int dij(int s/*出发点*/){
    memset(dis,0x3f,sizeof(dis));//初始化dis
    priority_queue<pii,vector<pii>,greater<pii>>q;//定义堆
    dis[s]=0;//初始化
    q.push(make_pair(s,0));
    while(!q.empty()){//只要堆里有货就搜索
        int u=q.top().first;//取出节点编号
        ll d=q.top().second;//取出节点dis值
        q.pop(); //弹出
        if(dis[u]!=d)continue;//判断有没有被再次更新,如果更新就证明已近搜索过了(为什么是已近搜索过了呢?因为假如被更新了,dis肯定是被更新小了的所以一定会在前面被优先搜索过一次)
        for(int v=1;v<=n;v++){//松弛可到达的节点并扔进堆里继续更新
            if(dis[u]+d[u][v]<dis[v]){
                dis[v]=dis[u]+d[u][v];
                q.push(make_pair(v,dis[v]));
            }
        }
    }
    //完
}

//读入为邻接矩阵,存储为邻接表
int main(){
    cin>>n;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++){
            cin>>d[i][j];
            e[i][++cnt[i]]=make_pair(j,d[i][j]);
        }
    dij(1);//dij搜索
    return 0;
}

注意事项

重申一边,不能在有负权边的图里用!!!有负权边请用\(SPFA\)!!!有负权边和环就两个都不能用了QWQ

posted @ 2019-05-30 20:27  bossbaby  阅读(658)  评论(0编辑  收藏  举报