圖論演算法講解--最短路--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));
}
}
}
}