今天特意看了Dijkstra算法详解,发现 两篇博客讲得不错,挺透彻的,在此集中一下,方便阅读
【数据结构】有向图(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 = 0,A->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 = 0,A->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 = 0,A->C = 3,A->C->B = 5,A->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 = 0,A->C = 3,A->C->B = 5,A->C->D = 6,A->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 = 0,A->C = 3,A->C->B = 5,A->C->D = 6,A->C->E =7,A->C->D->F = 9 | U集合已空,查找完毕 |
算法实现:
伪代码
DIJSTRA(G,w,s)
1 INITIALIZE-SINGLE-SOURCE(G,s)
2 S ← Φ
3 Q ← V[G]
4 while Q≠Φ
5 do u EXTRACT-MIN(Q)
6 S ← S∪{u}
7 for each vertex v∈Adj[u]
8 do RELAX(u,v,w)
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) << " ";
}
既然说是最短路,我们当然要有贪心的思想,这里狄克斯特拉完美的做到了这一点。那在算法当中,是如何利用这个贪心思想的呢?我们这里对应一个图来看。(图不咋好看,轻吐槽T T。)
我们假设2是起点,想要走到终点 4,显然我们有两种走法,而且显而易见,走2-> 1-> 4这条路是最短的。我们不希望走2->4这条路。我们通过1这个点,能把从2->4的路径复杂化(多走一步(多转个弯))但是却能够缩短路径耗时的操作,我们理解为松弛操作,我们完成dijkstra的整个算法的过程,无非就是不断的在松弛的过程。我们希望走的路径短,那我们必然要走很多弯路- -*
我们这里对应完整算法的图来理解:
我们这里定义图的编号为:
1 2 3
4 5 6
7 8 9
图1:初始化的图,其中包含边的权值(耗时)。(这里图是有向图)。
图2:确定起点,然后向能直接走到的点走一下,记录此时的估计值:2 6 9.。
图3:找到距离起点最近的点,是正东边的那个点,这时候我们耗费权值为2。然后我们进行松弛操作,从起点到其东南方的点直接到的权值耗费为6,但是我们通过刚刚选定的点,我们找到了到这个点更近的方式,所以这个时候我们说从起点到其东南方向的点的权值更新值从6变成了5。这个时候我们就完成了第一次松弛操作。
图4:依旧是找距离起点最近的点。然后松弛我们发现这个时候从起点到其东南方的点的耗费权值从5又变成了4.这个时候我们完成了第二个松弛。
之后的方式同上:选定距离起点最近的点v。然后通过点v进行松弛操作。我们发现能够通过增加走到目的地方式的复杂度(多转弯)的方式我们能够松弛掉权值,使得耗费的权值更小。
读者请自己跑一遍上边的图,大概的算法思维也就掌握了。
然后我们对应代码和图一起探究:
{if(!vis[k])d[k]=min(d[k],d[v]+w[v][k]);}}}
上图为松弛操作的效果图。其中我加了两个蓝色的中途点,表示我们这三个点并不一定是像最初的图那样的三个点,他们其中是有复杂的行进路程的。这样我们就很容易看的出来,是如何进行松弛操作的了~。
对于dijkstra是否能解无向图的问题,这里答案很明确:可以,因为无向图就是特殊的有向图,也可以理解为双向图。这里我们对应HDU的2544进行练习:
最短路
Time Limit: 5000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 46167 Accepted Submission(s): 203482 1 1 2 3 3 3 1 2 5 2 3 5 3 1 2 0 0
3 2