二之再續、Dijkstra 演算法+fibonacci堆的逐步c實現

來源:互聯網
上載者:User

二之再續、Dijkstra 演算法+fibonacci堆的逐步c實現

作者:JULY、二零一一年三月十八日
出處:http://blog.csdn.net/v_JULY_v
----------------------------------

 

引言:
    來考慮一個問題,
平面上6個點,A,B,C,D,E,F,假定已知其中一些點之間的距離,
現在,要求A到其它5個點,B,C,D,E,F各點的最短距離。

如所示:

     

經過,我們可以輕而易舉得到A->B,C,D,E,F各點的最短距離:

目的            路徑              最短距離
A=>A,      A->A                0
A=>B,    A->C->B         3+2=5
A=>C,      A->C                3
A=>D,    A->C->D          3+3=6
A=>E,    A->C->E           3+4=7
A=>F,   A->C->D->F      3+3+3=9

    我想,如果是單單出上述一道填空題,要你答出A->B,C,D,E,F各點的最短距離,
一個小學生,掰掰手指,也能在幾分鐘之內,填寫出來。

    我們的問題,當然不是這麼簡單,上述只是一個具體化的例子而已。
實際上,很多的問題,如求圖的最短路徑問題,就要用到上述方法,不斷比較、不斷尋找,以期找到最短距離的路徑,此類問題,便是Dijkstra 演算法的應用了。當然,還有BFS演算法,以及更高效的A*搜尋演算法。

    A*搜尋演算法已在本BLOG內有所詳細的介紹,本文咱們結合fibonacci堆實現Dijkstra 演算法。
即,Dijkstra + fibonacci堆 c實現。

    我想了下,把一個演算法研究夠透徹之後,還要編寫代碼去實現它,才叫真正掌握了一個演算法。本BLOG內經典演算法研究系列,已經寫了18篇文章,十一個演算法,所以,還有10多個演算法,待我去實現。

代碼風格
    實現一個演算法,首先要瞭解此演算法的原理,瞭解此演算法的原理之後,便是寫代碼實現。
在開啟編譯器之前,我先到網上搜尋了一下“Dijkstra 演算法+fibonacci堆實現”。

    發現:網上竟沒有過 Dijkstra + fibonacci堆實現的c代碼,而且如果是以下幾類的代碼,我是直接跳過不看的:

1、沒有注釋(看不懂)。
2、沒有排版(不舒服)。
3、冗餘繁雜(看著煩躁)。

 

fibonacci堆實現Dijkstra 演算法

    ok,閑話少說,咱們切入正題。下面,咱們來一步一步利用fibonacci堆實現Dijkstra 演算法吧。
前面說了,要實現一個演算法,首先得明確其演算法原理及思想,而要理解一個演算法的原理,又得知道發明此演算法的目的是什麼,即,此演算法是用來幹什麼的?

    由前面的例子,我們可以總結出:Dijkstra 演算法是為瞭解決一個點到其它點最短距離的問題。
我們總是要找源點到各個目標點的最短距離,在尋路過程中,如果新發現了一個新的點,發現當源點到達前一個目的點路徑通過新發現的點時,路徑可以縮短,那麼我們就必須及時更新此最短距離。

    ok,舉個例子:如我們最初找到一條路徑,A->B,這條路徑的最短距離為6,後來找到了C點,發現若A->C->B點路徑時,A->B的最短距離為5,小於之前找到的最短距離6,所以,便得此更新A到B的最短距離:為5,最短路徑為A->C->B.

    好的,明白了此演算法是幹什麼的,那麼咱們先用虛擬碼嘗試寫一下吧(有的人可能會說,不是吧,我現在,什麼都還沒搞懂,就要我寫代碼了。額,你手頭不是有資料麼,如果全部所有的工作,都要自己來做的話,那就是一個浩大的工程了。:D。)。

    咱們先從演算法導論上,找來Dijkstra 演算法的虛擬碼如下:

DIJKSTRA(G, w, s)
1  INITIALIZE-SINGLE-SOURCE(G, s)  //1、初始化結點工作
2  S ← Ø
3  Q ← V[G]   //2、插入結點操作
4  while Q ≠ Ø
5      do u ← EXTRACT-MIN(Q)   //3、從最小隊列中,抽取最小點工作
6         S ← S ∪{u}
7         for each vertex v ∈ Adj[u]
8             do RELAX(u, v, w)  //4、鬆弛操作。

    虛擬碼畢竟與能在機子上編譯啟動並執行代碼,還有很多工作要做。
首先,咱們看一下上述虛擬碼,可以看出,基本上,此Dijkstra 演算法主要分為以下四個步驟:

1、初始化結點工作
2、插入結點操作
3、從最小隊列中,抽取最小點工作
4、鬆弛操作。
 

    ok,由於第2個操作涉及到斐波那契堆,比較複雜一點,咱們先來具體分析第1、2、4個操作:

1、得用O(V)的時間,來對最短路徑的估計,和對前驅進行初始化工作。

INITIALIZE-SINGLE-SOURCE(G, s)
1  for each vertex v ∈ V[G]
2       do d[v] ← ∞
3          π[v] ← NIL      //O(V)
4  d[s] 0

    我們根據上述虛擬碼,不難寫出以下的代碼:

void init_single_source(Graph *G,int s)
{
    for (int i=0;i<G->n;i++) {
        d[i]=INF;
        pre[i]=-1;
    }
    d[s]=0;
}

2、插入結點到隊列的操作

2  S ← Ø
3  Q ← V[G]   //2、插入結點操作

代碼:
      for (i=0;i<G->n;i++)
       S[i]=0;

4、鬆弛操作。
首先得理解什麼是鬆弛操作:
    Dijkstra 演算法使用了鬆弛技術,對每個頂點v<-V,都設定一個屬性d[v],用來描述從源點s到v的最短路徑上權值的上界,稱為最短路徑的估計。
     RELAX(u, v, w)
     1  if d[v] > d[u] + w(u, v)
     2     then d[v] ← d[u] + w(u, v)
     3          π[v] ← u        //O(E)

同樣,我們不難寫出下述代碼:
     void relax(int u,int v,Graph *G)
     {
         if (d[v]>d[u]+G->w[u][v])
        {
            d[v] = d[u]+G->w[u][v];    //更新此最短距離
            pre[v]=u;     //u為v的父結點
        }
     }

    再解釋一下上述relax的代碼,其中u為v的父母結點,當發現其父結點d[u]加上經過路徑的距離G->w[u][v],小於子結點到源點的距離d[v],便得更新此最短距離。
    請注意,說的明白點:就是本來最初A到B的路徑為A->B,現在發現,當A經過C到達B時,此路徑距離比A->B更短,當然,便得更新此A到B的最短路徑了,即是:A->C->B,C 即成為了B的父結點(如此解釋,我相信您已經明朗。:D。)。
    即A=>B <== A->C->B,執行賦值操作。

    ok,第1、2、4個操作步驟,咱們都已經寫代碼實現了,那麼,接下來,咱們來編寫第3個操作的代碼:3、從最小隊列中,抽取最小點工作。

    相信,你已經看出來了,我們需要構造一個最小優先隊列,那用什麼來構造最小優先隊列列?對了,堆。什麼堆最好,效率最高,呵呵,就是本文要實現的fibonacci堆。

    為什麼?ok,請看最小優先隊列的三種實現方法比較:

         EXTRACT-MIN + RELAX
I、  簡單方式:  O(V*V + E*1)
II、 二叉/項堆: O(V*lgV + |E|*lgV)
       源點可達:O(E*lgV)
       稀疏圖時,有E=o(V^2/lgV),
            =>   O(V^2) 
III、斐波那契堆:O(V*lgV + E)

    其中,V為頂點,E為邊。好的,這樣我們就知道了:Dijkstra 演算法中,當用費伯納西堆作優先隊列時,演算法時間複雜度為O(V*lgV + E)

    額,那麼接下來,咱們要做的是什麼列?當然是要實現一個fibonacci堆了。可要怎麼實現它,才能用到我們
Dijkstra 演算法中列?對了,寫成一個庫的形式。庫?呵呵,是一個類。

        ok,以下就是這個fibonacci堆的實現://FibonacciHeap.h<br />#ifndef _FIBONACCI_HEAP_H_INCLUDED_<br />#define _FIBONACCI_HEAP_H_INCLUDED_</p><p>#include <functional><br />#include <algorithm></p><p>template<typename T><br />struct Fib_node<br />{<br /> Fib_node* ns_; //後驅結點<br /> Fib_node *pt_; //父母結點<br /> Fib_node* ps_; //前驅結點<br /> Fib_node* fc_; //頭結點<br /> int rank_; //孩子結點<br /> bool marked_; //孩子結點是否刪除的標記<br /> T* pv_;<br /> Fib_node(T* pv = 0) : pv_(pv) { }<br /> T& value(void) { return *pv_; }<br /> void set_src(T* pv) { pv_ = pv; }<br />}; //Fib_node的資料結構</p><p>template<class Node, class OD><br />Node* merge_tree(Node*a, Node* b, OD small) //合并結點<br />{<br /> if(small(b->value(), a->value()))<br /> swap(a, b);<br /> Node* fc = a->fc_;<br /> a->fc_ = b;<br /> a->ns_ = a->ps_ = a->pt_ = 0;<br /> ++a->rank_;</p><p> b->pt_ = a; //a為b的父母<br /> b->ns_ = fc; //第一個結點賦給b的前驅結點<br /> b->ps_ = 0;<br /> if(fc != 0)<br /> fc->ps_ = b;<br /> return a;<br />}</p><p>template<typename Node><br />void erase_node(Node* me) //刪除結點<br />{<br /> Node* const p = me->pt_;<br /> --p->rank_;<br /> if(p->fc_ == me) //如果me是頭結點<br /> {<br /> if((p->fc_ = me->ns_) != 0)<br /> me->ns_->ps_ = 0;<br /> }<br /> else<br /> {<br /> Node *prev = me->ps_;<br /> Node *next = me->ns_; //可能為0<br /> prev->ns_ = next;<br /> if(next != 0)<br /> next->ps_ = prev;<br /> }<br />}</p><p>template<class Node, class OD><br />Node* merge_fib_heap(Node* a, Node* b, OD small) //調用上述的merge_tree合并fib_heap。<br />{<br /> enum {SIZE = 64}; //<br /> Node* v[SIZE] = {0};<br /> int k;<br /> while(a != 0)<br /> {<br /> Node* carry = a;<br /> a = a->ns_;<br /> for(k = carry->rank_; v[k] != 0; ++k)<br /> {<br /> carry = merge_tree(carry, v[k], small);<br /> v[k] = 0;<br /> }<br /> v[k] = carry;<br /> }<br /> while(b != 0)<br /> {<br /> Node* carry = b;<br /> b = b->ns_;<br /> for(k = carry->rank_; v[k] != 0; ++k)<br /> {<br /> carry = merge_tree(carry, v[k], small);<br /> v[k] = 0;<br /> }<br /> v[k] = carry;<br /> }<br /> Node** t = std::remove(v, v+SIZE, (Node*)0);<br /> int const n = t - v;<br /> if(n > 0)<br /> {<br /> for(k = 0; k < n - 1; ++k)<br /> v[k]->ns_ = v[k+1];<br /> for(k = 1; k < n; ++k)<br /> v[k]->ps_ = v[k-1];<br /> v[n-1]->ns_ = v[0]->ps_ = 0;<br /> }<br /> return v[0];<br />}</p><p>template<typename T, class OD = std::less<T> ><br />struct Min_fib_heap //抽取最小結點<br />{<br /> typedef Fib_node<T> Node;<br /> typedef Node Node_type;</p><p> Node* roots_;<br /> Node* min_; //pointer to the minimum node<br /> OD less_; </p><p> Min_fib_heap(void): roots_(0), min_(0), less_() { }<br /> bool empty(void) const { return roots_ == 0; }<br /> T& top(void) const { return min_->value(); }</p><p> void decrease_key(Node* me) //刪除<br /> { //precondition: root_ not zero<br /> if(less_(me->value(), min_->value()))<br /> min_ = me;<br /> cascading_cut(me);<br /> }<br /> void push(Node* me) //壓入<br /> {<br /> me->pt_ = me->fc_ = 0;<br /> me->rank_ = 0;<br /> if(roots_ == 0)<br /> {<br /> me->ns_ = me->ps_ = 0;<br /> me->marked_ = false;<br /> roots_ = min_ = me;<br /> }<br /> else<br /> {<br /> if(less_(me->value(), min_->value()))<br /> min_ = me;<br /> insert2roots(me);<br /> }<br /> }<br /> Node* pop(void) //彈出<br /> {<br /> Node* const om = min_;<br /> erase_tree(min_);<br /> min_ = roots_ = merge_fib_heap(roots_, min_->fc_, less_);<br /> if(roots_ != 0) //find new min_<br /> {<br /> for(Node* t = roots_->ns_; t != 0; t = t->ns_)<br /> if(less_(t->value(), min_->value()))<br /> min_ = t;<br /> }<br /> return om;<br /> }<br /> void merge(void) //合并<br /> {<br /> if(empty()) return;<br /> min_ = roots_ = merge_fib_heap(roots_, (Node*)0, less_);<br /> for(Node* a = roots_->ns_; a != 0; a = a->ns_)<br /> if(less_(a->value(), min_->value() ))<br /> min_ = a;<br /> }<br />private:<br /> void insert2roots(Node* me) //插入<br /> { //precondition: 1) root_ != 0; 2) me->value() >= min_->value()<br /> me->pt_ = me->ps_ = 0;<br /> me->ns_ = roots_;<br /> me->marked_ = false;<br /> roots_->ps_ = me;<br /> roots_ = me;<br /> }<br /> void cascading_cut(Node* me) //斷開<br /> { //precondition: me is not a root. that is me->pt_ != 0<br /> for(Node* p = me->pt_; p != 0; me = p, p = p->pt_)<br /> {<br /> erase_node(me);<br /> insert2roots(me);<br /> if(p->marked_ == false)<br /> {<br /> p->marked_ = true;<br /> break;<br /> }<br /> }<br /> }<br /> void erase_tree(Node* me) //刪除<br /> {<br /> if(roots_ == me)<br /> {<br /> roots_ = me->ns_;<br /> if(roots_ != 0)<br /> roots_->ps_ = 0;<br /> }<br /> else<br /> {<br /> Node* const prev = me->ps_;<br /> Node* const next = me->ns_;<br /> prev->ns_ = next;<br /> if(next != 0)<br /> next->ps_ = prev;<br /> }<br /> }<br />}; //Min_fib_heap的類</p><p>template<typename Fitr><br />bool is_sorted(Fitr first, Fitr last)<br />{<br /> if(first != last)<br /> for(Fitr prev = first++; first != last; prev = first++)<br /> if(*first < *prev) return false;<br /> return true;<br />}<br />template<typename Fitr, class OD><br />bool is_sorted(Fitr first, Fitr last, OD cmp)<br />{<br /> if(first != last)<br /> for(Fitr prev = first++; first != last; prev = first++)<br /> if(cmp(*first, *prev)) return false;<br /> return true;<br />}

        由於本BLOG日後會具體闡述這個斐波那契堆的各項操作,限於篇幅,在此,就不再囉嗦解釋上述程式了。

        ok,實現了fibonacci堆,接下來,咱們可以寫Dijkstra 演算法的代碼了。為了版述清晰,再一次貼一下此演算法的虛擬碼:

DIJKSTRA(G, w, s)
1  INITIALIZE-SINGLE-SOURCE(G, s)
2  S ← Ø
3  Q ← V[G]   //第3行,INSERT操作,O(1)
4  while Q ≠ Ø
5      do u ← EXTRACT-MIN(Q)   //第5行,EXTRACT-MIN操作,V*lgV
6         S ← S ∪{u}
7         for each vertex v ∈ Adj[u]
8             do RELAX(u, v, w)  //第8行,RELAX操作,E*O(1)

     編寫的Dijkstra演算法的c代碼如下:void Dijkstra(int s, T d[], int p[])<br />{<br /> //尋找從頂點s出發的最短路徑,在d中儲存的是s->i的最短距離<br /> //p中儲存的是i的父節點<br /> if (s < 1 || s > n)<br /> throw OutOfBounds(); </p><p> //路徑可到達的頂點列表,這裡可以用上述實現的fibonacci堆代碼。<br /> Chain<int> L; </p><p> ChainIterator<int> I;<br /> //初始化d, p, and L<br /> for (int i = 1; i <= n; i++)<br /> {<br /> d[i] = a[s][i]; </p><p> if (d[i] == NoEdge)<br /> {<br /> p[i] = 0;<br /> }<br /> else<br /> {<br /> p[i] = s;<br /> L.Insert(0,i);<br /> }<br /> } </p><p> //更新d, p<br /> while (!L.IsEmpty())<br /> {<br /> //尋找最小d的點v<br /> int *v = I.Initialize(L);<br /> int *w = I.Next();<br /> while (w)<br /> {<br /> if (d[*w] < d[*v])<br /> v = w; </p><p> w = I.Next();<br /> } </p><p> int i = *v;<br /> L.Delete(*v);<br /> for (int j = 1; j <= n; j++)<br /> {<br /> if (a[i][j] != NoEdge<br /> && (!p[j] || d[j] > d[i] + a[i][j])) //d[i]是父節點<br /> {<br /> // 重新整理更小的d[j]<br /> d[j] = d[i] + a[i][j]; </p><p> // 如果j沒有父節點,則添加到L<br /> if (!p[j])<br /> L.Insert(0,j); </p><p> // 更新父節點<br /> p[j] = i;<br /> }<br /> }<br /> }<br />}

更好的代碼,還在進一步修正中。日後,等完善好後,再發布整個工程出來。

完。

 

著作權。轉載本BLOG內任何文章,請以超連結形式註明出處。謝謝,各位。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.