迪傑斯特拉(dijkstra)算法詳解

在圖的應用中,有一個很重要的需求:我們需要知道從某一個點開始,到其他所有點的最短路徑。

    這其中,Dijkstra算法是典型的最短路徑算法。它的關鍵思想是以起始點爲中心,向外一層層擴散,直到擴展到終點爲止。Dijkstra算法能夠得出最短路徑的最優解,不過它需要遍歷計算的節點相當多,所以效率不高。

 

    首先,用最通俗的語言解釋。假定有3個頂點,ABC,如圖:

【數據結構】有向圖(4.dijkstra算法詳解)

    要求A到其餘各點的最短路徑。很明顯,ACAB更短。有疑惑的是從A->B的最短距離,要麼是直接A->B的邊,要麼是A經過CB的邊更短。我們首先找到最短的邊(A->C),然後在此基礎上擴展,於其餘邊去對比找到最小值。頂點再進一步擴充增加,按照這個思想,我們總可以找到A到所有點的最短路徑。

 

 

算法描述:

    從節點1開始到其餘各點的dijkstra算法,其中Wa->b表示邊a->b的權,d(i)即爲最短路徑值,頂點集合爲V={1,2,3...n}

    1.置集合S={1},置頂點集合U={2,3,4...n},數組d(1)=0,d(i)=W1->i1i之間存在邊)or 無窮大(1i之間不存在邊);

 

    2.U中,令d(j)=min{d(i),i屬於U},將jU中移至S中,若U爲空集則算法結束,否則轉3

 

    3.對全部i屬於U,如果存在邊j->i,那麼置d(i)=min{d(i), d(j) + Wj->i},2

 

    Dijkstra算法的思想爲;G=(V, E)是一個帶權有向圖,把圖中頂點集合V分爲兩部分,第一組爲已求出最短路徑的頂點集合(用S表示,初始時S中只有源點,以後每求出一條最短路徑,就將頂點加入到S中,直到所有頂點都加入到S中,算法結束),第二組爲其餘未求出最短路徑的頂點集合(用U表示),按最短路徑的長度次序依次將第二組中的頂點加入到第一組中。

 

     在加入過程中,總保持着從源點vS中各頂點的最短路徑不大於從源點vU中各頂點的最短路徑長度。此外,每個頂點對應一個距離,S中的頂點的距離即爲源點v到該點的最短路徑長度。U中頂點的距離,是從v到此頂點只包括S中的頂點爲中間頂點的當前最短距離。

 

 

算法具體步驟

    1.初始時,S中只有源點,即S = {v}v的距離爲0(到自己的距離爲0)。U包含除v外地所有其他頂點,U中頂點u距離爲邊上的權(若vu存在邊)或 ∞ (vu不存在邊,即u不是v的出邊鄰接點)

 

    2.U中選取一個距離v最小的頂點k加入到S中(選定的距離就是vk的最短路徑)

 

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

 

    4.重複步驟23直到所有的頂點都加入到S

 

 

複雜度分析:

    實現方式的不同,可能有n次或者n-1次外層的循環,這裏取n次。

    步驟2每一輪的比較步驟會是nn-1n-2...1

    步驟3每一輪的更新步驟會是n-1n-2...1

    這樣計算出結果大致爲 n²

    Dijkstra算法的時間複雜度爲O(n^2)

    空間複雜度取決於存儲方式,鄰接矩陣爲O(n^2)

 

再看一個例子:

 【數據結構】有向圖(4.dijkstra算法詳解)

 

步驟

S集合中      

U集合中

1   

選入A,此時S ={A}

此時最短路徑A->A = 0

A爲中間點,從A開始找

U = {B, C, D, E, F}

A->B = 6

A->C = 3

A->U中其他頂點 = 

其中A->C = 3 權值爲最小,路徑最短

2

選入上一輪中找到的最短路徑的頂點C,此時S = {A, C}

此時最短路徑A->A = 0A->C = 3

C爲中間點,從A->C=3這條最短路徑開始新一輪查找

U = {B, D, E, F}

A->C->B = 5(比上面的A->B = 6要小)

替換B的權值爲更小的A->C->B = 5

A->C->D = 6

A->C->E = 7

A->C->U中其他頂點 = 

其中A->C->B = 5 最短

3

選入B,此時S = {A, C, B}

此時最短路徑 A->A = 0A->C = 3

A->C->B = 5

B爲中間點,從A->C->B = 5這條最短路徑開始新一輪查找

U = {D, E, F}

A->C->B->D = 10(比上面的A->C->D = 6大,不替換,保持D的權值爲A->C->D=6)

A->C->B->U中其他頂點 = 

其中 A->C->D = 6 最短

4

選入D,此時 S = {A, C, B, D}

此時最短路徑 A->A = 0A->C = 3A->C->B = 5A->C->D = 6

D爲中間點,從A->C->D = 6這條最短路徑開始新一輪查找

U = {E, F}

A->C->D->E = 8(比上面步驟2中的A->C->E = 7要長,保持E的權值爲A->C->E =7)

A->C->D->F = 9

其中A->C->E = 7最短

5

選入E,此時 S = {A, C, B, D ,E}

此時最短路徑 A->A = 0A->C = 3A->C->B = 5A->C->D = 6A->C->E =7,

E爲中間點,從A->C->E = 7這條最短路徑開始新一輪查找

U = {F}

A->C->E->F = 12(比第4步中的A->C->D->F = 9要長,保持F的權值爲A->C->D->F = 9)

其中A->C->D->F =9最短

6

選入F,此時 S = {A, C, B, D ,E, F}

此時最短路徑 A->A = 0A->C = 3A->C->B = 5A->C->D = 6A->C->E =7,A->C->D->F = 9

U集合已空,查找完畢

 

算法實現:

僞代碼

     Dijkstra算法解決了有向圖G=(V, E)上帶全的單源最短路徑問題,但要求所有的邊權非負)。因此,假定每條邊(uv)E,有w(uv)0

 

     Dijksra算法中設置了一個頂點集合S,從源點s到集合中的頂點的最終最短路徑的權值均已確定。算法反覆選擇具有最短路徑估計的頂點uV-S,並將u加入到S中,對u的所有出邊進行鬆弛。在下面的算法實現中,用到了頂點的最小優先隊列Q,排序關鍵字爲頂點的d值。

 

DIJSTRA(Gws)

  1 INITIALIZE-SINGLE-SOURCE(Gs)

  2 S ← Φ

  3 Q ← V[G]

  4 while Q≠Φ

  5 do u EXTRACT-MIN(Q)

  6 S ← S{u}

  7 for each vertex vAdj[u]

  8 do RELAX(uvw)

C++代碼實現

    這是前面代碼中複製過來的,仍然是用模板跟容器實現的,可以做些修改使用數組或其他數據結構及實現方式。

 

template<typename vertexNameType, typename weight>
int OLGraph<vertexNameType, weight>::Dijkstra(IN const vertexNameType vertexName1)
{
int sourceIndex = getVertexIndex(vertexName1); //
獲取源點在容器中索引值
if (-1 == sourceIndex)
{
cerr << "There is no vertex " << endl;
return false;
}
int nVertexNo = getVertexNumber(); //
獲取頂點數

vector<bool> vecIncludeArray; //頂點是否已求出最短路徑
vecIncludeArray.assign(nVertexNo, false); //
初始化容器
vecIncludeArray[sourceIndex] = true;

vector<weight> vecDistanceArray; //路徑值容器
vecDistanceArray.assign(nVertexNo, weight(INT_MAX)); //
將所有頂點到源點的初始路徑值爲正無窮
vecDistanceArray[sourceIndex] = weight(0); //
源點到自己距離置0

vector<int> vecPrevVertex; //路徑中,入邊弧尾頂點編號(即指向自己那個頂點的編號)
vecPrevVertex.assign(nVertexNo, sourceIndex); //
指向所有頂點的弧尾都初始爲源點,源點指向所有頂點

getVertexEdgeWeight(sourceIndex, vecDistanceArray); //得到源點到其餘每個頂點的距離

int vFrom, vTo;

while(1)
{
weight minWeight = weight(INT_MAX);
vFrom = sourceIndex;
vTo = -1;
for (int i = 0; i < nVertexNo; i++) //
找出還沒求出最短距離的頂點中,距離最小的一個
{
if (!vecIncludeArray[i] && minWeight > vecDistanceArray[i])
{
minWeight = vecDistanceArray[i];
vFrom = i;
}
}
if (weight(INT_MAX) == minWeight) //
若所有頂點都已求出最短路徑,跳出循環
{
break;
}
vecIncludeArray[vFrom] = true; //
將找出的頂點加入到已求出最短路徑的頂點集合中

//更新當前最短路徑,只需要更新vFrom頂點的鄰接表即可,因爲所有vFrom指向的邊都在鄰接表中
Edge<weight> *p = m_vertexArray[vFrom].firstout;
while (NULL != p)
{
weight wFT = p->edgeWeight;
vTo = p->headvex;
if (!vecIncludeArray[vTo] && vecDistanceArray[vTo] > wFT + vecDistanceArray[vFrom]) //
當前頂點還未求出最短路徑,並且經由新中間點得路徑更短
{
vecDistanceArray[vTo] = wFT + vecDistanceArray[vFrom];
vecPrevVertex[vTo] = vFrom;
}
p = p->tlink;
}

}

for (int i = 0; i < nVertexNo; i++) //輸出最短路徑
{
if (weight(INT_MAX) != vecDistanceArray[i])
{
cout << getData(sourceIndex) << "->" << getData(i) << ": ";
DijkstraPrint(i, sourceIndex, vecPrevVertex);
cout << " " << vecDistanceArray[i];
cout << endl;
}
}

return 0;
}

template<typename vertexNameType, typename weight>
void OLGraph<vertexNameType, weight>::DijkstraPrint(IN int index, IN int sourceIndex, IN vector<int> vecPreVertex)
{
if (sourceIndex != index)
{
DijkstraPrint(vecPreVertex[index], sourceIndex, vecPreVertex);
}
cout << getData(index) << " ";
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章