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));
}
}
}
}