A simple dispiction of dijkstra


前言

  • \(SPFA\)算法由於它上限 \(O(NM) = O(VE)\)的時間復雜度,被卡掉的幾率很大.在算法競賽中,我們需要一個更穩定的算法:\(dijkstra\).

什么是\(dijkstra\)?

  • \(dijkstra\)是一種單源最短路徑算法,時間復雜度上限為\(O(n^2)\)(朴素),在實際應用中較為穩定\(;\)加上堆優化之后更是具有\(O((n+m)\log_{2}n)\)的時間復雜度,在稠密圖中有不俗的表現.

\(dijkstra\)的原理/流程?

  • \(dijkstra\)本質上的思想是貪心,它只適用於不含負權邊的圖.
  • 我們把點分成兩類,一類是已經確定最短路徑的點,稱為"白點",另一類是未確定最短路徑的點,稱為"藍點"
  • \(dijkstra\)的流程如下\(:\)
  • \(1.\) 初始化\(dis[start] = 0,\)其余節點的\(dis\)值為無窮大.
  • \(2.\) 找一個\(dis\)值最小的藍點\(x,\)把節點\(x\)變成白點.
  • \(3.\) 遍歷\(x\)的所有出邊\((x,y,z),\)\(dis[y] > dis[x] + z,\)則令\(dis[y] = dis[x] + z\)
  • \(4.\) 重復\(2,3\)兩步,直到所有點都成為白點\(.\)
  • 時間復雜度為\(O(n^2)\)

\(dijkstra\)為什么是正確的

  • 當所有邊長都是非負數的時候,全局最小值不可能再被其他節點更新.所以在第\(2\)步中找出的藍點\(x\)必然滿足\(:dis[x]\)已經是起點到\(x\)的最短路徑\(.\)我們不斷選擇全局最小值進行標記和拓展,最終可以得到起點到每個節點的最短路徑的長度

圖解

  • (令\(start = 1\))
  • 開始時我們把\(dis[start]\)初始化為\(0\),其余點初始化為\(inf\)
    初始化
  • 第一輪循環找到\(dis\)值最小的點\(1\),將\(1\)變成白點,對所有與\(1\)相連的藍點的\(dis\)值進行修改,使得\(dis[2]=2,dis[3]=4,dis[4]=7\)
    1
  • 第二輪循環找到\(dis\)值最小的點\(2\),將\(2\)變成白點,對所有與\(2\)相連的藍點的\(dis\)值進行修改,使得\(dis[3]=3,dis[5]=4\)
    2
  • 第三輪循環找到\(dis\)值最小的點\(3\),將\(3\)變成白點,對所有與\(2\)相連的藍點的\(dis\)值進行修改,使得\(dis[4]=4\)
    3
  • 接下來兩輪循環分別將\(4,5\)設為白點,算法結束,求出所有點的最短路徑
  • 時間復雜度\(O(n^2)\)

為什么\(dijkstra\)不能處理有負權邊的情況?

  • 我們來看下面這張圖
    4
  • \(2\)\(3\)的邊權為\(-4\),顯然從\(1\)\(3\)的最短路徑為\(-2\) \((1->2->3).\)但在循環開始時程序會找到當前\(dis\)值最小的點\(3\),並標記它為白點.
  • 這時的\(dis[3]=1,\)然而\(1\)並不是起點到\(3\)的最短路徑.因為\(3\)已經被標為白點,所以\(dis[3]\)不會再被修改了.我們在邊權存在負數的情況下得到了錯誤的答案.

\(dijkstra\)的堆優化?

  • 觀察\(dijkstra\)的流程,發現步驟\(2\)可以優化

  • 怎么優化呢?

  • 我會zkw線段樹!我會斐波那契堆!

  • 我會堆!

  • 我們可以用堆對\(dis\)數組進行維護,用\(O(\log_{2}n)\)的時間取出堆頂元素並刪除,用\(O(\log_{2}n)\)遍歷每條邊,總復雜度\(O((n+m)\log_{2}n)\)

  • 范例代碼:

#include<bits/stdc++.h>

const int MaxN = 100010, MaxM = 500010;

struct edge
{
    int to, dis, next;
};

edge e[MaxM];
int head[MaxN], dis[MaxN], cnt;
bool vis[MaxN];
int n, m, s;

inline void add_edge( int u, int v, int d )
{
    cnt++;
    e[cnt].dis = d;
    e[cnt].to = v;
    e[cnt].next = head[u];
    head[u] = cnt;
}

struct node
{
    int dis;
    int pos;
    bool operator <( const node &x )const
    {
        return x.dis < dis;
    }
};

std::priority_queue<node> q;


inline void dijkstra()
{
    dis[s] = 0;
    q.push( ( node ){0, s} );
    while( !q.empty() )
    {
        node tmp = q.top();
        q.pop();
        int x = tmp.pos, d = tmp.dis;
        if( vis[x] )
            continue;
        vis[x] = 1;
        for( int i = head[x]; i; i = e[i].next )
        {
            int y = e[i].to;
            if( dis[y] > dis[x] + e[i].dis )
            {
                dis[y] = dis[x] + e[i].dis;
                if( !vis[y] )
                {
                    q.push( ( node ){dis[y], y} );
                }
            }
        }
    }
}


int main()
{
    scanf( "%d%d%d", &n, &m, &s );
    for(int i = 1; i <= n; ++i)dis[i] = 0x7fffffff;
    for( register int i = 0; i < m; ++i )
    {
        register int u, v, d;
        scanf( "%d%d%d", &u, &v, &d );
        add_edge( u, v, d );
    }
    dijkstra();
    for( int i = 1; i <= n; i++ )
        printf( "%d ", dis[i] );
    return 0;
}

例題

  • 入門模板:P3371
  • 進階模板:P4779
  • 其余例題請右轉洛谷題庫,搜索"最短路"

后記

  • 本文部分內容摘自李煜東《算法競賽進階指南》和《信息學競賽一本通》
  • 友情提示:正權圖請使用\(dijkstra\)算法,負權圖請使用\(SPFA\)算法


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM