1. 程式人生 > 實用技巧 >最短路演算法Dijkstra

最短路演算法Dijkstra

一、最短路的分類

n 表示點的數量 m表示邊的數量
1、單源最短路
求一個點到所有點的最短路
(1) 所有邊權都是正數
①樸素的dijkstra演算法 O(n^2)
時間複雜度和邊數無關 適合稠密圖(邊數多)
適合邊數是n^2級別的情況
②堆優化版的dijkstra演算法O(mlogn)
適合邊數是n級別的情況(節點多 邊數少)
(2)存在負權邊
①Bellman-Ford演算法 O(nm)
②SPFA O(m) 對Bellman-Ford演算法的優化

2、多源最短路
起點和終點都不確定,求任意兩個點的最短路
Floyd演算法 O(n^3)

二、樸素版Dijkstra演算法

1、初始化距離
dis[1]=0 一號點到起點的距離為0,
dis[i]=+∞ 其餘的點到起點的距離為無窮
2、迭代
集合s 當前已經確定最短路的點
迴圈n次 for i:n
迭代找到不在s中的距離最近的點 t
把t加入到s中
用t更新其他點的距離
更新也迴圈n次 所以總的時間複雜度是n^2;
(看1號點走到x的距離是否大於從1號點走到t,再從t走到x。如果大於則更新1號點到x點的距離)

3、本演算法適合於稠密圖 用鄰接矩陣來存

注意:無向圖和有向圖的最短路是沒有區別的

#include<bits/stdc++.h>
using namespace std;
const int N = 510;
int n, m;
int g[N][N];//鄰接矩陣儲存稠密有向圖
int dis[N]; //表示確定的點的最短距離
bool vis[N];//這個點是否被訪問過
int Dijkstra() {
    memset(dis, 0x3f, sizeof(dis));//其餘點初始化距離為無窮
    dis[1] = 0;//起點初始化距離為0
    for (int i = 1; i <= n; i++) {//首先要遍歷N次每次確定一個節點
        int t = -1;
        for (int j = 1; j <= n; j++) {//第二個for是找出確定距離的點中的最小的節點
            if (!vis[j] && (t == -1 || dis[t] > dis[j])) {
                t = j;
            }
        }
        vis[t] = true;
        for (int j = 1; j <= n; j++) {//第二個for 根據選中的節點 更新距離
            dis[j] = min(dis[j], dis[t] + g[t][j]);
        }
    }
    if (dis[n] == 0x3f3f3f3f)
        return -1;
    else return dis[n];
}
int main()
{
    memset(g, 0x3f, sizeof(g));
    cin >> n >> m;
    for (int i = 1; i <= m; i++) {
        int a, b, c;
        cin >> a >> b >> c;
        g[a][b] = min(g[a][b], c);
    }
    cout << Dijkstra() << endl;
    return 0;
}

三、堆優化版的Dijkstra

為了實現在一堆數中找到最小的數 ->需要用堆實現
1、堆的實現
①手寫堆 裡面n個數
②優先佇列 裡面m個數 (複雜度稍微提高)
本演算法適合稀疏圖->儲存方式使用鄰接表
注意:用鄰接表儲存 不需要特殊處理重邊
邊的權不為1的時候鄰接表加邊模板
add(int a,int b,in c){
e[idx]=b;
ne[idx]=h[a];
wei[idx]=c;
h[a]=idx++;
}
基本思路
1.首先初始化起點的距離為0 其餘點的距離為無窮
2.將起點加入到優先佇列中 優先佇列維護最小值
3.根據堆頂元素的權值和他能到達的點,更新其他點的距離,將更新距離後的點加入到佇列中

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
const int M = 2 * N;
typedef pair<int, int> PII;
int n, m;
int h[N], e[M], wei[M], ne[M], idx;
bool vis[M];
int dis[N];
void add(int a, int b, int c) {//有權重的加邊方式
    e[idx] = b;
    wei[idx] = c;
    ne[idx] = h[a];
    h[a] = idx++;
}
int Dijkstra() {
    memset(dis, 0x3f, sizeof(dis));
    dis[1] = 0;
    priority_queue<PII, vector<PII>, greater<PII>> pq;//小根堆 距離小的排在前面
    pq.push(make_pair(0, 1));
    while (pq.size()) {
        PII now = pq.top();
        pq.pop();               //直接根據已經知道的點和這個點到其他點的權重更新能到達的點的距離
        int distance = now.first;
        int ser = now.second;
        if (vis[ser])  //如果這個點已經加入堆中過了 那麼剩下的是冗餘的 不可能是最短路
            continue;
        vis[ser] = true;
        for (int i = h[ser]; i != -1; i = ne[i]) {
            int j = e[i]; //j代表a->b的b 即ser能到達的點
            if (dis[j] > dis[ser] + wei[i]) {
                dis[j] = dis[ser] + wei[i];//當前的值可能是無窮 如果當前的值大於從ser點到它的值則更新
//這裡的dis[ser]和distance是相等的
                pq.push(make_pair(dis[j], j));//將其入隊
            }
        }
    }
    if (dis[n] == 0x3f3f3f3f)
        return -1;
    else return dis[n];

}
int main()
{
    cin >> n >> m;
    memset(h, -1, sizeof(h));
    for (int i = 1; i <= m; i++) {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c);
    }
    cout << Dijkstra() << endl;
    //system("PAUSE");
    return 0;
}