Dijkstra算法堆優化詳解


DIJ算法的堆優化

DIJ算法的時間復雜度是\(O(n^2)\)的,在一些題目中,這個復雜度顯然不滿足要求。所以我們需要繼續探討DIJ算法的優化方式。

堆優化的原理

堆優化,顧名思義,就是用堆進行優化。我們通過學習朴素DIJ算法,明白DIJ算法的實現需要從頭到尾掃一遍點找出最小的點然后進行松弛。這個掃描操作就是坑害朴素DIJ算法時間復雜度的罪魁禍首。所以我們使用小根堆,用優先隊列來維護這個“最小的點”。從而大大減少DIJ算法的時間復雜度。

堆優化的代碼實現

說起來容易,做起來難。

我們明白了上述的大體思路之后,就可以動手寫這個代碼,但是我們發現這個代碼有很多細節問題沒有處理。

首先,我們需要往優先隊列中push最短路長度,但是它一旦入隊,就會被優先隊列自動維護離開原來的位置,換言之,我們無法再把它與它原來的點對應上,也就是說沒有辦法形成點的編號到點權的映射。

我們用pair解決這個問題。

pair是C++自帶的二元組。我們可以把它理解成一個有兩個元素的結構體。更刺激的是,這個二元組有自帶的排序方式:以第一關鍵字為關鍵字,再以第二關鍵字為關鍵字進行排序。所以,我們用二元組的first位存距離,second位存編號即可。

然后我們發現裸的優先隊列其實是大根堆,我們如何讓它變成小根堆呢?

有兩種方法,第一種是把第一關鍵字取相反數,取出來的時候再取相反數。第二種是重新定義優先隊列:

priority_queue<int,vector<int>,greater<int> >q;

解決了這些問題,我們愉快地繼續往下寫,后來我們發現,寫到松弛的時候,我們很顯然要把松弛后的新值也壓入優先隊列中去,這樣的話,我們又發現一個問題:優先隊列中已經存在一個同樣編號的二元組(即第二關鍵字相同),我們沒有辦法刪去它,也沒有辦法更新它。那么在我們的隊列和程序運行的時候,一定會出現bug。

怎么辦呢??

我們在進入循環的時候就開始判斷:如果有和堆頂重復的二元組,就直接pop掉,成功維護了優先隊列元素的不重復。

所以我們得到了堆優化的代碼:

priority_queue<pair<int,int> >q;
void dijkstra(int start)
{
    memset(dist,0x3f,sizeof(dist));
    memset(v,0,sizeof(v));
    dist[start]=0;
    q.push(make_pair(0,start));
    while(!q.empty())
    {
        while(!q.empty() && (-q.top().first)>dist[q.top().second])
            q.pop();
        if(!q.empty())
            return;
        int x=q.top().second;
        q.pop();
        for(int i=head[x];i;i=nxt[i])
        {
            int y=to[i];
            if(dist[y]>dist[x]+val[i])
            {
                dist[y]=dist[x]+val[i];
                q.push(make_pair(-dist[y],y));
            }
        }
    }
}


免責聲明!

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



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