1. 程式人生 > >最短路演算法-迪傑斯特拉演算法

最短路演算法-迪傑斯特拉演算法

1.圖中的最短路徑求解的演算法花樣百出,但是最常用的就是那幾種很耳熟能詳的演算法。今天我們來了解一下十分常用的迪傑斯特拉演算法。

2.dj是一種求非負權值的單源最短路演算法。通俗的講就是求這樣的問題:在圖中的兩個點s,t並且這個圖中沒有負的邊權,那麼求解從s到t的最短的路徑權值和是多少。首先說明幾個特點,注意dj的使用範圍,非負權值的單源最短路。但是雖然我們求的是s到t的最短路,但是求解過程中會把s到圖中所有點的最短路都求出來。這是dj的一些特性,為什麼會有這樣的特性,我們來看一下原理就明白了。

3.dj演算法的原理:首先看圖

                                                    

假設我們要求0到4的最短路,那麼其實dj是這樣執行的,它的基本思想就是貪心和鬆弛。他首先把點分為兩個集合s和t,s代表已經求得的最短路徑的點,t代表還未求得最短路的點。初始位置s裡面只有源點,也就是0其他節點都在t裡。然後會有一個dis陣列,儲存當前狀態下源點到各個節點的最短路,dis初始化的結果就是源點到各個點之間的路徑長度,如果有直接的連邊,就是這個連邊的長度,如果沒有就是INF,例如上圖中我們可以知道dis[1]=10,dis[4]=100,dis[2]=inf。然後就可以開始工作了。工作的流程是這樣的,首先我們從源點出發,找到距離最近的點(貪心),沒錯,我們在這個圖中找到的就是10,找到之後更新dis對應的值(當然這裡的更新是沒有意義的,因為是一樣的),然後我們鬆弛這是很重要的,或者說這就是dj的精髓。怎麼鬆弛呢?是這樣工作的,我們現在已經有了1點,1被加入s集合中,我們通過1可以到達2,並且路徑的長度是50,所以0到達2的路徑長度就是60,因為之前0到2是沒有直接的路徑的,所以我們這個時候就可以把路徑更新到60,這樣就完成了鬆弛。當然了,dj並不僅僅是這麼運作的,當找到1之後,他會計算髮現到2的長度是60,但是到3的長度是30(這裡的到指的是源點到其他點),所以他會選擇小的去,所以第二個被加入的節點是3而不是2(這也是一種貪心的策略)。然後這個時候我們依然使用上邊的方法,我們發現這個時候可以到達的點有2,4,但是到達4的最短路徑是90,到達2的是50(通過3來鬆弛),這個時候2倍加入s集合,2被加入s集合之後,我們又可以通過2來鬆弛4,所以到4的最短路徑就是60了。這樣所有點被加入集合。最短路演算法完成。

4.疑惑的解決:開始的時候我們就提出了兩個問題。1.dj是基與非負權值的。2.dj雖然是求兩個點之間的最短路,但是會把源點到其他所有點的最短路都求出來。現在看完原理之後我想就很好回答這個問題了,首先為什麼是非負權值,因為dj的鬆弛過程決定的,如果出現負邊,那麼鬆弛的時候就會在哪個具有負邊的地方迴圈。其次,雖然是求兩點之間的最短路,但是任何一點的加入都有可能會對這兩點之間的最短路產生鬆弛。所以我們需要把所有的點都加進去試一遍。

5.dj的複雜度是n方的,這個與圖是怎麼存的沒有關係。下面給出實現的程式碼:

/*Dijkstra求單源最短路徑 */
 
#include <iostream>
#include<stack>
#define M 100
#define N 100
using namespace std;

typedef struct node
{
    int matrix[N][M];      //鄰接矩陣 
    int n;                 //頂點數 
    int e;                 //邊數 
}MGraph; 

void DijkstraPath(MGraph g,int *dist,int *path,int v0)   //v0表示源頂點 
{
    int i,j,k;
    bool *visited=(bool *)malloc(sizeof(bool)*g.n);
    for(i=0;i<g.n;i++)     //初始化 
    {
        if(g.matrix[v0][i]>0&&i!=v0)
        {
            dist[i]=g.matrix[v0][i];
            path[i]=v0;     //path記錄最短路徑上從v0到i的前一個頂點 
        }
        else
        {
            dist[i]=INT_MAX;    //若i不與v0直接相鄰,則權值置為無窮大 
            path[i]=-1;
        }
        visited[i]=false;
        path[v0]=v0;
        dist[v0]=0;
    }
    visited[v0]=true;
    for(i=1;i<g.n;i++)     //迴圈擴充套件n-1次 
    {
        int min=INT_MAX;
        int u;
        for(j=0;j<g.n;j++)    //尋找未被擴充套件的權值最小的頂點 
        {
            if(visited[j]==false&&dist[j]<min)
            {
                min=dist[j];
                u=j;        
            }
        } 
        visited[u]=true;
        for(k=0;k<g.n;k++)   //更新dist陣列的值和路徑的值 
        {
            if(visited[k]==false&&g.matrix[u][k]>0&&min+g.matrix[u][k]<dist[k])
            {
                dist[k]=min+g.matrix[u][k];
                path[k]=u; 
            }
        }        
    }    
}

void showPath(int *path,int v,int v0)   //列印最短路徑上的各個頂點 
{
    stack<int> s;
    int u=v;
    while(v!=v0)
    {
        s.push(v);
        v=path[v];
    }
    s.push(v);
    while(!s.empty())
    {
        cout<<s.top()<<" ";
        s.pop();
    }
} 

int main(int argc, char *argv[])
{
    int n,e;     //表示輸入的頂點數和邊數 
    while(cin>>n>>e&&e!=0)
    {
        int i,j;
        int s,t,w;      //表示存在一條邊s->t,權值為w
        MGraph g;
        int v0;
        int *dist=(int *)malloc(sizeof(int)*n);
        int *path=(int *)malloc(sizeof(int)*n);
        for(i=0;i<N;i++)
            for(j=0;j<M;j++)
                g.matrix[i][j]=0;
        g.n=n;
        g.e=e;
        for(i=0;i<e;i++)
        {
            cin>>s>>t>>w;
            g.matrix[s][t]=w;
        }
        cin>>v0;        //輸入源頂點 
        DijkstraPath(g,dist,path,v0);
        for(i=0;i<n;i++)
        {
            if(i!=v0)
            {
                showPath(path,i,v0);
                cout<<dist[i]<<endl;
            }
        }
    }
    return 0;
}