圖的那些事兒——Dijkstra和Floyd

最短路問題

Dijkstra算法
說到最短路問題,我相信只要是學習過計算機的人都有據說過Dijkstra他老人家,他對程序的貢獻遠不止一個算法。程序員

1 提出「goto有害論」;
2 提出信號量和PV原語;
3 解決了「哲學家聚餐」問題;
4 最短路徑算法(SPF)和銀行家算法的創造者;
5 第一個Algol 60編譯器的設計者和實現者;
6 THE操做系統的設計者和開發者;算法

按照他本身的稱呼,他是一個程序員。不得不說,這樣的程序員實在是太偉大了。數組

讓咱們回到Dijkstra算法上。
這個算法的核心是維護d[i]=>i號結點和起點s距離的估值,之因此是估值,是由於它可能並非真的最短值。要經歷一個過程,纔可以成爲真正的最短值。markdown

此次咱們先看算法好了。數據結構

清除全部點的標號(全部點都是未知的)
設d[0]=0,d[i]=INF(無限大)
循環n次{
    #在未知的點中,尋找出d值最小的結點x
    *標記x爲已知
    對於從x出發的全部邊(x,y)更新 d[y]=min(d[y],d[x]+w[x][y])   
}

咱們看到了這個過程(標了*號的這一行)。
是當d[x]爲當前全部未知點中的最小值時,別的全部的到達x的走法都是繞遠路、捨近求遠。因此,這時咱們能夠肯定 d[x]就是x點和起點s的最短距離!
咱們來寫一個簡單的版本好了學習

//v 標記是否已知
//
memset(v,0,sizeof(v));
for(int i=0;i<n;i++) d[i]=(i==0?0:INF);
for(int i=0;i<n;i++){
    int x,m=INF;
    /*查找未知點中d值最小的結點x*/
    for(int j=0;j<n;j++)if(v[j]&&d[j]<m) m=d[x=j];
    v[x]=1;
    /*以x爲中間點,更新別的點的d值*/
    for(int k=0;k<n;k++) d[k]=min(d[k],d[x]+w[x][k]); 
}

代碼中帶有註釋的兩處其實均可以優化。
查找最小結點這種工做其實對於一個優先隊列來講很是合適。
這個隊列中擁有d值以及其對應的結點號(d[i]和i)
還須要定義>操做符優化

struct HeapNode{
    int d,i;
    bool operator < (const HeapNode& rhs) const{
        return d>rhs.d; 
    } 
}

在正式完成算法以前,咱們首先對數據結構進行些許該進。另外一個能夠優化的地方也在這裏,將w[i][j]這樣一個N^2的數組轉換爲一個vector< int> Gi[maxn]來存儲邊號,用vector< Edge> Edges來存儲邊。ui

struct Edge{
    int from,to;
    int dist;
    Edge(int x,int y,int d):from(u),to(v),dist(d){}
}
struct Dijstra{
    vector<int> G[maxn];
    vector<Edge> edges;
    int m,n;
    int d[mniaxn],v[maxn];
    int p[maxn];/*保存父結點*/
    void init(int n){
        this->n=n;
        for(auto item:G)item.clear();
        edges.clear();
    }
    void AddEdge(int from,int to,int dist){
        edges.push_back(Edge(from,to,dist));
        m=edges.size();
        G[from].push_back(m-1);/*m-1恰好爲這個邊在edges中的索引*/
    }
    void dijkstra(int s){
    ...
    }
}

主算法:this

void Dijkstra(int s){
    priority_queue<HeapNode> Q;
    for(int i=0;i<n;i++) d[i]=INF;
    d[s]=0;
    memset(done,0,sizeof(done));
    Q.push((HeadNode){0,s});
    while(!Q.empty()){
        HeapNode x=Q.top();Q.pop();
        int u=x.u;
        if(done[u]) continue;
        done[u]=true;
        for(int i=0;i<G[u].size();i++){
            Edge& e=edge[G[u][i]];
            if(d[e.to]>d[u]+e.dist){
             d[e.to]=d[u]+e.dist;
             p[e.to]=G[u][i];
             Q.push((HeapNode){d[e.to],e.to})
            }
        }
    }
}

是否是有點累了呢。不要緊,只要看懂了第一段代碼,對Dijkstra的貪心思想熟記於心就能夠了!
可是Dijkstra算法在面對有負權邊時就無能爲力了,有負環的話就意味着最短路徑不存在!
這個時候咱們須要另外一種算法Bellman-Ford算法
Bellman-Ford
咱們先直接上代碼看看spa

for(int i=0;i<n;i++) d[i]=INF;
d[0]=0;
for(int k=0;k<n-1;k++)//迭代n-1次
    for(int i=0;i<m;i++)//檢查每條邊
    {
        int x=u[i],y=v[i];
        if(d[x]<INF) d[y]=min(d[y],d[x]+w[i]);//鬆弛
    }

須要指出的是:Bellman-Ford算法很是低,其優化這裏先不給出

咱們應該關注的重點在於Floyd算法:

for(int k=0;k<n;k++)//做爲中點
    for(int i=0;i<n;i++)
        for(int j=0;j<n;j++)
            d[i][j]=min(d[i][j],d[i][k]+d[k][j]);//鬆弛

Floyd求出的是各個點對的距離。

相關文章
相關標籤/搜索