Bootstrap

单源最短路径 dijkstra()

前言

首先,单源最短路径问题(Single Source Shortest Path , SSSP 问题)是说: 给定一张有向图 G= (V,E) , V 是这个图的点集, E = 是边集 , 节点以【1,n】 之间的连续整数编号,(x,y,z) 描述一条从 x 出发 , 达到 y ,长度(权值)为z 的有向边 。设1号点为起点,求长度为n的数组dist , 其中 dist[i] 代表从起点1到节点 i 的最短距离

算法流程

1 初始化dist[1] = 0 , 其余节点的dist值为无穷大

2 找出一个未被标记的 , dist[x]的最小的节点x, 然后标记节点x

3. 扫描节点 x (其上) 的所有出边(x,y,z) 若 dist[y] > dist[x]+ z , 则使用dist[x] + z 更新 dist[y];

4. 重复上述 2~3 步骤,知道所有节点都被标记。

整个dijkstra() 基于贪心思想(由局部最优解得出全局最优) , 但它只能适用于所有边的长度(权值)都是非负数的图 ,当边长z都是非负数时,全局最小值不可能再被其他节点更新, 故在第一步中选出的节点 x 必定满足:dist[x] 已经是起点到x的最短路径 。 我们不断选择全局最小值进行标记和扩展,最终课得到起点1 到每个 节点的最短路径的长度。 注:倘若边权出现负数, 那么依照贪心思想, 局部最优的特点 , 我们最终是用dijkstra所得的局部最优,并不能满足全局最优 ,由此贪心证明失败,算法错误!(解决带有负权边的图可使用bellman-ford或者 spfa(相当于队列优化版本的bellman-ford) ,这里只介绍dijkstra算法一种,后续会更新其他算法)

上代码!!!

首先说明,dijkstra有两种不同的实现方法,一个是无优化版的和一个队列优化版的。

说明了具体思路 再来用代码来讲讲这两种实现方式:

无优化版本的dijkstra()  O(n*n)

int dijsktra() { 
    // 先将dist 初始化为无穷大 , memset方法大家自行百度       
    memset(d,0x3f,sizeof d); 
    // 对应第一步 , 将 d[1] 赋为 0 
    d[1]= 0;
    for(int i=0;i<n-1;i++) {  // 遍历n-1次
        int t= 0;
        for(int j=1;j<=n;j++) {
            // 对应第二步 , 通过遍历所有带你 , 找出 x  
            if(!st[j] && (!t || d[t]>d[j])) t = j;  
        }
        // 也对应第二步 标记 x
        st[t] = 1;
        // 对应第三步 , 扫描出边,同时用 x 对所有边更新
        for(int k=1;k<=n;k++) {
            d[k] = min(d[k],d[t]+g[t][k]);
        }
    }
}

上面程序的时间复杂度为 O(n*n) , 主要瓶颈在于第一步的寻找全局最小的过程。

队列优化版本    O(mlogn)

typedef pair<int,int> PII;
int n,m;
int e[N],ne[N],h[N],w[N],idx;
int d[N],st[N];
 // 领接表 , 使用数组模拟链表, 具体可看我关于数组模拟链表的文章
void add(int a,int b,int c){
    e[idx] = b;
    w[idx] = c;
    ne[idx] = h[a];
    h[a] = idx++;
}

int dijsktra() {
    // 使用优先队列
    priority_queue<PII,vector<PII> , greater<PII>> q;
    // 对应第一步 初始化所有dist , 并令 d[1] = 0
    memset(d,0x3f,sizeof d);
    d[1] = 0;
    // c++  优先队列默认以pair中的first为排序项  first对应边长(权值) , second对应其边
    q.push({0,1});
    while(q.size()) {
        // 找到当前最短的边 x\u  第二步
        auto u = q.top();
        q.pop();
        // 定义当前 点的  边
        int ver = u.y;
        if(st[ver]) continue; // 如果已经被选过了 相当于已经被标记了 , 就跳过
        // 标记当前这个边
        st[ver] = 1;
        // 因为使用的是领接表存储方式 , 在这里便对领接表进行遍历 h初始化为 -1    
        // 由 当前最短的边(相当于 x) 对所有出边进行更新 对应第三步
        for(int i=h[ver];i!=-1;i=ne[i]) {
            int j = e[i];
            if(d[j]>d[ver]+w[i]) {
                d[j] = d[ver] + w[i];
                q.push({d[j],j});
            }
        }
    }
}

典例示范 

 849. Dijkstra求最短路 I - AcWing题库

ac代码

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int N = 510;
int n,m;
int a,b,c;
int g[N][N],d[N],st[N];
int dijsktra() {
    memset(d,0x3f,sizeof d);
    d[1]= 0;
    for(int i=0;i<n-1;i++) {
        int t= 0;
        for(int j=1;j<=n;j++) {
            if(!st[j] && (!t || d[t]>d[j])) t = j;
        }
        st[t] = 1;
        for(int k=1;k<=n;k++) {
            d[k] = min(d[k],d[t]+g[t][k]);
        }
    }
    if(d[n]==0x3f3f3f3f) return 0;
    return d[n];
}
int main() {
    cin>>n>>m;
    memset(g,0x3f,sizeof g);
    while(m--) {
        scanf("%d%d%d",&a,&b,&c);
        // 使用领接矩阵存储
        g[a][b] = min(g[a][b],c);
    }
    int t = dijsktra();
    if(!t) cout<<"-1";
    else cout<<t;
    return 0;
}

850. Dijkstra求最短路 II - AcWing题库

ac代码

#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
#define x first 
#define y second
using namespace std;
typedef pair<int,int> PII;
const int N = 150010;
int n,m;
int e[N],ne[N],h[N],w[N],idx;
int d[N],st[N];

void add(int a,int b,int c){
    e[idx] = b;
    w[idx] = c;
    ne[idx] = h[a];
    h[a] = idx++;
}
int dijsktra() {
    priority_queue<PII,vector<PII> , greater<PII>> q;
    memset(d,0x3f,sizeof d);
    d[1] = 0;
    q.push({0,1});
    while(q.size()) {
        auto u = q.top();
        q.pop();
        int ver = u.y;
        if(st[ver]) continue;
        st[ver] = 1;
        for(int i=h[ver];i!=-1;i=ne[i]) {
            int j = e[i];
            if(d[j]>d[ver]+w[i]) {
                d[j] = d[ver] + w[i];
                q.push({d[j],j});
            }
        }
    }
    if(d[n]>=0x3f3f3f3f) return 0;
    return d[n];
}
int main() {
    cin>>n>>m;
    memset(h,-1,sizeof h);
    while(m--) {
        int a,b,c;
        cin>>a>>b>>c;
           // 使用领接表存储元素
        add(a,b,c);
    }
    int t = dijsktra();
    if(!t) cout<<"-1";
    else cout<<t;
    return 0 ;
}

结言

纯纯的算法萌新 , 学习ing , 如有差错 ,请各位大佬多多指教

;