1. 程式人生 > >演算法筆記--最短路徑之dijkstra演算法

演算法筆記--最短路徑之dijkstra演算法

                                                                                                    1  最短路徑演算法

在日常生活中,我們如果需要常常往返A地區和B地區之間,我們最希望知道的可能是從A地區到B地區間的眾多路徑中,那一條路徑的路途最短。最短路徑問題是圖論研究中的一個經典演算法問題,旨在尋找圖(由結點和路徑組成的)中兩結點之間的最短路徑。 演算法具體的形式包括:

(1)確定起點的最短路徑問題:即已知起始結點,求最短路徑的問題。

(2)確定終點的最短路徑問題:與確定起點的問題相反,該問題是已知終結結點,求最短路徑的問題。在無向圖中該問題與確定起點的問題完全等同,在有向圖中該問題等同於把所有路徑方向反轉的確定起點的問題。

(3)確定起點終點的最短路徑問題:即已知起點和終點,求兩結點之間的最短路徑。

(4)全域性最短路徑問題:求圖中所有的最短路徑。

用於解決最短路徑問題的演算法被稱做“最短路徑演算法”, 有時被簡稱作“路徑演算法”。最常用的路徑演算法有四種:1.Dijkstra(迪傑斯特拉)------------解決單源最短路問題。(貪心的思想)----條件:非負權值。

2.Bellman-Ford(貝爾曼-福特)--------解決單源最短路問題。(逐個遍歷每一條邊)

3.Floyd(弗洛伊德)---------------解決全源最短路問題。(dp的思想)

4.SPFA(Shortest Path Faster Algorithm)-----------解決單元最短路問題。(佇列實現,是bellman-Ford演算法的一種改進)

本文(下文)主要研究Dijkstra演算法的單源演算法。

      Dijkstra's algorithm, conceived by Dutchcomputer scientistEdsger Dijkstra in 1956 and published in 1959,[1][2] is a graph search algorithm that solves the single-sourceshortest path problem for agraph with nonnegativeedge path costs, producing ashortest path tree

. This algorithm is often used inrouting and as a subroutine in other graph algorithms.

     翻譯:Dijkstra演算法,是由荷蘭電腦科學家Edsger Dijkstra演算法在1956和1959出版,它是一個圖的搜尋演算法,解決單源最短路徑問題的一個非負邊路徑成本圖,產生一個最短路徑樹。該演算法是經常使用的路由和在其他圖形演算法子程式

  Dijkstra演算法是典型最短路演算法,用於計算一個節點到其他所有節點的最短路徑。主要特點是以起始點為中心向外層層擴充套件,直到擴充套件到終點為止。Dijkstra演算法能得出最短路徑的最優解,但由於它遍歷計算的節點很多,所以效率低。

Dijkstra演算法是很有代表性的最短路演算法,在很多專業課程中都作為基本內容有詳細的介紹,如資料結構,圖論,運籌學等等。 

Dijkstra演算法思想為:設G=(V,E)是一個帶權有向圖,把圖中頂點集合V分成兩組,第一組為已求出最短路徑的頂點集合(用S表示,初始時S中只有一個源點,以後每求得一條最短路徑 , 就將加入到集合S中,直到全部頂點都加入到S中,演算法就結束了),第二組為其餘未確定最短路徑的頂點集合(用U表示),按最短路徑長度的遞增次序依次把第二組的頂點加入S中。在加入的過程中,總保持從源點v到S中各頂點的最短路徑長度不大於從源點v到U中任何頂點的最短路徑長度。此外,每個頂點對應一個距離,S中的頂點的距離就是從v到此頂點的最短路徑長度,U中的頂點的距離,是從v到此頂點只包括S中的頂點為中間頂點的當前最短路徑長度。

1)初始時,S只包含源點,即S=,v的距離為0。U包含除v外的其他頂點,U中頂點u距離為邊上的權(若v與u有邊)或)(若u不是v的出邊鄰接點)。

(2)從U中選取一個距離v最小的頂點k,把k,加入S中(該選定的距離就是v到k的最短路徑長度)。

(3)以k為新考慮的中間點,修改U中各頂點的距離;若從源點v到頂點u(u U)的距離(經過頂點k)比原來距離(不經過頂點k)短,則修改頂點u的距離值,修改後的距離值的頂點k的距離加上邊上的權。

(4)重複步驟(2)和(3)直到所有頂點都包含在S中。

如下圖,設A為源點,求A到其他各頂點(B、C、D、E、F)的最短路徑。線上所標註為相鄰線段之間的距離,即權值。(注:此圖為隨意所畫,其相鄰頂點間的距離與圖中的目視長度不能一一對等)

圖一:Dijkstra無向圖

Dijkstra最短路徑演算法

演算法執行步驟如下表:【注:名為“Dijkstra演算法過程”的圖】

Dijkstra最短路徑演算法

單源最短路徑問題:即在圖中求出給定頂點到其它任一頂點的最短路徑。在弄清楚如何求算單源最短路徑問題之前,必須弄清楚最短路徑的最優子結構性質。

一.最短路徑的最優子結構性質

   該性質描述為:如果P(i,j)={Vi....Vk..Vs...Vj}是從頂點i到j的最短路徑,k和s是這條路徑上的一箇中間頂點,那麼P(k,s)必定是從k到s的最短路徑。下面證明該性質的正確性。

   假設P(i,j)={Vi....Vk..Vs...Vj}是從頂點i到j的最短路徑,則有P(i,j)=P(i,k)+P(k,s)+P(s,j)。而P(k,s)不是從k到s的最短距離,那麼必定存在另一條從k到s的最短路徑P'(k,s),那麼P'(i,j)=P(i,k)+P'(k,s)+P(s,j)<P(i,j)。則與P(i,j)是從i到j的最短路徑相矛盾。因此該性質得證。

二.Dijkstra演算法

   由上述性質可知,如果存在一條從i到j的最短路徑(Vi.....Vk,Vj),Vk是Vj前面的一頂點。那麼(Vi...Vk)也必定是從i到k的最短路徑。為了求出最短路徑,Dijkstra就提出了以最短路徑長度遞增,逐次生成最短路徑的演算法。譬如對於源頂點V0,首先選擇其直接相鄰的頂點中長度最短的頂點Vi,那麼當前已知可得從V0到達Vj頂點的最短距離dist[j]=min{dist[j],dist[i]+matrix[i][j]}。根據這種思路,

假設存在G=<V,E>,源頂點為V0,U={V0},dist[i]記錄V0到i的最短距離,path[i]記錄從V0到i路徑上的i前面的一個頂點。

1.從V-U中選擇使dist[i]值最小的頂點i,將i加入到U中;

2.更新與i直接相鄰頂點的dist值。(dist[j]=min{dist[j],dist[i]+matrix[i][j]})

3.知道U=V,停止。



參考文獻

[1]  黃國瑜、葉乃菁,資料結構,清華大學出版社,20018月第1

[3]  李春葆,資料結構教程,清華大學出版社,20051月第1

下面主要以HDU-OJ-1874講解此題。

題目大意概括:輸入n和m分別抽象表示n個點,m條邊,下面輸入m行,每行輸入a,b,x。表示a->b之間道路長度x,

輸入起點s,終點e。

輸出最短需要行走的距離。如果不存在從S到T的路線,就輸出-1.

程式碼的實現過程如下(程式碼中已經詳細寫明註釋,仔細體會程式碼):

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#define N 1000
#define inf 999999
using namespace std;

int map[N][N];// 記錄圖的兩點間路徑長度
int dis[N];// 表示當前點到源點的最短路徑長度
int vis[N];//visited陣列標記某點是否已訪問
int s,e;//宣告全域性變數s起點e終點

void dijkstra(int n)
{
    int i,j;
    int pos;
    for(i=0; i<n; i++)
    {
        dis[i]=map[i][s];//第一次給dis陣列賦值
    }
    vis[s]=1;//標記起點已經訪問過
    for(i=0; i<n-1; i++)//再執行n-1次,剩下的n-1個點
    {
        pos=s;
        int min=inf;
        for(j=0; j<n; j++)
        {
            if(!vis[j]&&dis[j]<min)
            {
                min=dis[j];//更新最小距離
                pos=j;//並且標記下標
            }
        }
        vis[pos]=1;//標記改點已經訪問過
        for(j=0; j<n; j++)
        {
            if(!vis[j]&&dis[j]>dis[pos]+map[j][pos])//更新與j直接相鄰頂點的dis值
                dis[j]=dis[pos]+map[j][pos];//這裡是此演算法更新的關鍵所在
        }
    }
    if(dis[e]!=inf)
        printf("%d\n",dis[e]);//打印出源點到最後一個頂點的最短路徑長度;
    else
        printf("-1\n");//否則列印-1;
}
int main()
{
    int n,m;
    int i,j;
    int a,b,x;//宣告變數
    while(scanf("%d %d",&n,&m)!=EOF)//迴圈輸入資料n和m的值
    {
        memset(vis,0,sizeof(vis));//清空vis陣列
        for(i=0; i<n; i++)
        {
            for(j=0; j<n; j++)
            {
                map[i][j]=inf;//所有權值初始化為最大,代表地圖上的點沒有連線
            }
            map[i][i]=0;//自身到自身的距離權值為0
        }
        for(i=0; i<m; i++)
        {
            scanf("%d %d %d",&a,&b,&x);
            if(map[a][b]>x)//更新
                map[a][b]=map[b][a]=x;//這樣表示無向圖
        }
        scanf("%d %d",&s,&e);//輸入起點和終點
        dijkstra(n);
    }
    return 0;
}
以上重點闡釋了最短路徑dijkstra演算法的實現過程。