1. 程式人生 > >圖論演算法講解--最短路--Dijkstra演算法

圖論演算法講解--最短路--Dijkstra演算法

一.緒論

要學習最短路演算法我們首先應該知道什麼是圖以及什麼是最短路。

圖在離散數學中的定義為:圖G=(V,E)是一個二元組(V,E)使得E⊆[V]的平方,所以E的元素是V的2-元子集。為了避免符號上的混淆,我們總是預設V∩B=Ø。集合V中的元素稱為圖G的定點(或節點、點),而集合E的元素稱為邊(或線)。通常,描繪一個圖的方法是把定點畫成一個小圓圈,如果相應的頂點之間有一條邊,就用一條線連線這兩個小圓圈,如何繪製這些小圓圈和連線時無關緊要的,重要的是要正確體現哪些頂點對之間有邊,哪些頂點對之間沒有邊。

簡單來講,一個圖就是由兩個集合構成,集合V儲存圖中的所有頂點,集合E儲存圖中所有的邊。

最短路的定義為:在一個圖中,每條邊都有一個權值,給出一個起點和終點,求出起點到終點之間權值最小的路徑,即是最短路。

二.Dijkstra概述

Dijkstra演算法用於解決單源最短路問題,所謂單源最短路就是指定一個起點,求出這個起點到其它所有點的最短路。 使用Dijkstra演算法還要求圖中不能有權值為負的邊,否則會出現死迴圈。

三.Dijkstra演算法流程

首先給出幾個定義: 1.s:表示單源最短路的起點。 2.d[v]:表示從起點到v點的距離,初始化d[1~n]=inf,d[s]=0,當Dijkstra演算法結束後,d[v]所儲存的即時由起點到v的最短路長度。 3.vst[v]:記錄頂點v是否已經更新過,初始化vst[1~n]=0,表示所有點都未被更新過。 4.val(u,v):表示從u點連向v點邊的權值。

演算法流程: 1.從圖中找出所有u可達的頂點v0~vm,對於v0~vm,若有d[v]> d[u]+val(u,v),則表示發現更短的路線,更新d[v]為d[u]+val(u,v)。 2.找出d[v]最小,且vst[v]=0的點,讓v作為新的源點,將vst[v]=1,表示頂點v已被更新。 3.重複上述過程,直到所有頂點都被更新。

從上述流程中不難發現,所謂Dijkstra演算法,就是依次讓每個頂點作為起點,更新最短路的過程。 具體過程,請結合演算法流程來看圖文演示,相信看完之後大家一定會理解Dijkstra演算法的思想。

四.圖文演示

1. 這裡寫圖片描述 初始狀態,將d[1~n]=inf,d[s]=0 以點M作為起點,即s=M

2. 這裡寫圖片描述 找出所有M可達的頂點,為X、W、E d[X]=inf > 10 ,更新d[X]=10 d[W]=inf > 5 ,更新d[W]=5 d[E]=inf > 8 ,更新d[E]=8 M已被使用,vst[M]=1

3. 這裡寫圖片描述 找出d中值最小且未被使用的點,發現d[W]=5最小,且vst[W]=0,未被使用,故將W作為新的起點 找出所有W可達頂點,為M、X、D、E vst[M]=1,已被更新過,故忽略 d[X]=10 > 5+3 ,更新d[X]=8 d[D]=inf > 5+9 ,更新d[D]=14 d[E]=8 >5+2 ,更新d[E]=7 W已被使用,vst[W]=1

4. 這裡寫圖片描述 找出d中值最小且未被使用的點,發現d[E]=7最小,且vst[7]=0,未被使用,故將E作為新的起點 找出所有E可達的頂點,為W、M、D vst[W]=1,已被更新過,忽略 vst[M]=1,已被更新過,忽略 d[D]=14 > 7+6 ,更新d[D]=13 E已被使用,vst[E]=1

5. 這裡寫圖片描述 找出d中值最小且未被使用的點,發現d[X]=8最小,且vst[X]=0,未被使用,故將X作為新的起點 找出所有X可達的頂點,為W、M、D vst[W]=1,已被更新過,忽略 vst[M]=1,已被更新過,忽略 d[D]=13 >8+1 ,更新d[D]=9 X已被使用,更新vst[X]=1

6. 這裡寫圖片描述 找出d中值最小且未被使用的點,發現d[D]=9最小,且vst[D]=0,未被使用,故將D作為新的起點 找出所有D可達的頂點,為E、W、X vst[E]=1,已被使用,忽略 vst[W]=1,已被使用,忽略 vst[X]=1,已被使用,忽略 D已被使用,更新vst[D]=1

7. 發現所有頂點都被使用過,演算法結束,此時d陣列中儲存的就是以M為起點,到達所有其它點的單源最短路長度

五.演算法實現及優化

致此,我相信大家都理解了演算法的流程了,下面通過程式碼來看如何具體實現Dijkstra,詳見註釋。

普通版本:O(n^2)
#include<stdio.h>
#include<iostream>
#include<cstring>
using namespace std;

const int maxv=1000;
const int inf=0x3f3f3f3f;
int cost[maxv][maxv];       //鄰接矩陣存圖
int d[maxv];
bool vst[maxv];
int N;                 //頂點的個數

void dijkstra(int s)
{
    memset(d,inf,sizeof(d));     //初始化d[1~N]=inf
    d[s]=0;                     //初始化d[s]=0
    fill(used,used+v,false);       //初始化vst[1~N]=0

    while(true)
    {
        int now=-1;
        for(int u=0;u<N;u++)
        {
            if(!used[u]&&(now==-1||d[u]<d[now])) //找到d最小,且vst=0的點作為新的起點
                now=u;
        }
        if(now=-1) break;           //now未被更新,即表示所有頂點都被使用過,演算法結束

        for(int v=0;v<N;v++)        //遍歷當前起點now能到達的所有點
        {
            if(d[v]>d[now]+cost[now][v])     //若d[v]>d[u]+val(u,v),則更新
                d[v]=d[now]+cost[now][v]
        }
        vst[now]=1;             //當前起點now已被使用過,vst[now]=1
    }
}
路徑還原:

除了最短路的長度,還要求出最短路的路徑

#include<stdio.h>
#include<iostream>
#include<cstring>
#include<vector>
using namespace std;

const int maxv=1000;
const int inf=0x3f3f3f3f;
int cost[maxv][maxv];       //鄰接矩陣存圖
int prev[maxv];          //儲存前驅點編號,是路徑還原的關鍵
int d[maxv];
bool vst[maxv];
int N;                 //頂點的個數

void dijkstra(int s)
{
    memset(d,inf,sizeof(d));     //初始化d[1~N]=inf
    d[s]=0;                     //初始化d[s]=0
    fill(used,used+v,false);       //初始化vst[1~N]=0

    while(true)
    {
        int now=-1;
        for(int u=0;u<N;u++)
        {
            if(!used[u]&&(now==-1||d[u]<d[now])) //找到d最小,且vst=0的點作為新的起點
                now=u;
        }
        if(now=-1) break;           //now未被更新,即表示所有頂點都被使用過,演算法結束

        for(int v=0;v<N;v++)        //遍歷當前起點now能到達的所有點
        {
            if(d[v]>d[now]+cost[now][v])     //若d[v]>d[u]+val(u,v),則更新
            {
                d[v]=d[now]+cost[now][v];
                prev[v]=now;                 //同時更新前驅結點編號
            }
        }
        vst[now]=1;             //當前起點now已被使用過,vst[now]=1
    }
}
vector <int>get_path(int t)         //路徑還原函式,將最短路徑存在path中
{
    vector<int>path;
    for(;t!=-1;t=prev[t])
        path.push_back(t);
    reverse(path.begin(),path.end());
    return path;
}
優先佇列優化:O(ElogV)
#include <bits/stdc++.h>
#include <vector>
#include <queue>
using namespace std;

const int MAX_N=1000;
const int MAX_V=1000;
const int INF=0x3f3f3f3f;
struct edge
{
    int to,cost;
};
typedef pair<int,int>P;    //first是最短距離,second是頂點編號
int N;                     //頂點的個數
vector<edge>G[MAX_N];       //存圖的方法有很多種,大家可以用自己喜歡的方式
int d[MAX_V];

void Dijstra(int s)
{
    //通過指定greater<P>引數,堆按照first從小到大順序取出值
    priority_queue< P,vector<P>,greater<P> > que;
    fill(d,d+N,INF);       //初始化,d[1~N]設為inf
    d[s]=0;               //初始化,d[s]=0
    que.push(P(0,s));
    while(!que.empty())
    {
        P now=que.top();         //now表示當前起點
        que.pop();
        int u=now.second;
        if(d[u]<now.first) continue;
        for(int i=0;i<G[u].size();i++)       //遍歷當前起點能到達的所有點
        {
            edge e=G[u][i];
            if(d[e.to]>d[u]+e.cost)         //如果d[v]>d[u]+val(u,v)則更新
            {
                d[e.to]=d[u]+e.cost;
                que.push(P(d[e.to],e.to));
            }
        }
    }
}