<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">一、定义</span>
<span style="font-family: 新宋体; font-size: 14px; orphans: 2; text-align: -webkit-auto; widows: 2; background-color: rgb(255, 255, 255);"> 最短路径定义:在图中假设边有权值,从一点u到另一点v,如果有多条路径,其中路径权值和最小的路径是最短路径(一条路径的权值等于这条路径上所有边权值加和)</span>
单源点最短路径定义:给定一个源点s,求s到所有点的最短路径。 二、解决的问题 Dijkstra解决的是单源点最短路径问题,解决的是满足特定条件的图的最短路径问题,条件如下: 1. 边权值 >= 0
Dijkstra算法是选出的最短路径权值是递增的,如果出现负权值的边,Dijkstra不能保证结果正确。
三、用到的技术 松弛: 最短路径算法都用到“松弛”技术,即是否能对当前s到v的距离进行改善?表示为:if d[v] > d[u]+w[u,v],不同算法对边松弛的次数和顺序不同。 四、算法详解 给出算法描述(参考<<算法导论>>第三版P383) Dijkstra(G,w,s) 1 INITIALIZE-SINGLE-SOUREC(G,s) 2 S=
Ø
3 Q=G.V
4 while Q!=
Ø
5 u=EXTRACT-MIN(Q)
6 S=S
∪{u}
7 for each vertex v∈G.adj[u]
8 RELAX(u,v,w)
符号解释:
INITIALIZE-SINGLE-SOUREC(G,s): 初始化操作,主要是两点,使源点s到所有顶点距离d[s,v]等于∞,以及对指示顶点是否已得到最短路径的数组finished[i]置false,(特别的: 初始化d[s] = 0) S: 大S表示已经得到最短路径的顶点集合 Q: 表示还未得到最短路径的顶点集合 s: 小s表示源点
EXTRACT-MIN(Q): 从未得到最短路径的顶点集合中选出一个当前d[s,v]最小的顶点v’,
v’就是这一轮选出的顶点,表示
v’已经找到最短路径 RELAX: 进行松弛操作,查看是否存在更短的路径,改善距离d[v]
算法解释: 第1行:初始化 第2行:已得到最短路径顶点集合S置空 第3行:未得到最短路径顶点集合Q初始化为图中所有顶点 第5-6行:
从未得到最短路径的顶点集合中选出一个当前d[s,v]最小的顶点v’,加入S集合,
v’就是这一轮选出的顶点,表示
v’已经找到最短路径
第7-8行:对上一步得到的v’,对v’与其邻接点中未访问过的边进行松弛,即查看通过v’到达其邻接点是否更近
重复4-8,V次,找出s到所有顶点的最短路径
Dijkstra 算法每轮选出一个顶点的最短路径,最短路径是以递增的过程不断选择出来。
下面用图和表来演示Dijkstra如何求得最短路径。
原图: 邻接矩阵:
给定源点V0,初始化得到表格Table 1 Table 1 初始化
i:表示第几次迭代 V
finished:表示当前次迭代选出的顶点(就是已经求得最短路径的顶点) S:表示已经得到最短路径的顶点集合 右下方的单元格中,上方表示当前V0到Vi的临时最短距离,下方表示临时最短路径
i=0 第0次迭代 选出当前距离最小顶点V0,将V0加入S集合,对V0出发的边进行松弛(if d[V] > d[V0] + w[V0,V’],更新,V’是V0邻接点),得到Table 2 Table 2 第0次迭代后结果
第0次迭代后,发现通过V0到达V2、V4、V5的距离更近,更新表格 1) 更新距离 d[i] = d[V0] + w[V0,Vi] 2) 更新被更新顶点的最短路径,先将V0的最短路径复制过来,再加上(V0,Vi) V0最短路径是V0,加上(V0,V2)构成V2当前最短路径V0->V2,加上(V0,V4)构成V4当前最短路径V0->V4,
加(V0,V5)构成V5当前最短路径V0->V5 i=1 第1次迭代 选出当前距离最小顶点V2,将V2加入S集合,对V2出发的边进行松弛(if d[V] > d[V2] + w[V2,V’],更新,V’是V2邻接点),得到Table 3 Table 3 第1次迭代后的结果
第1次迭代后,发现通过V2到达V3的距离更近,更新表格 1) 更新距离 d[i] = d[V2] + w[V2,Vi] 2) 更新被更新顶点的最短路径,先将V2的最短路径复制过来,再加上(V2,Vi) V2最短路径是V0->V2,加上(V2,V3)构成V3当前最短路径V0->V2->V3
i=2 第2次迭代 选出当前距离最小顶点V4,将V4加入S集合,对V4出发的边进行松弛(if d[V] > d[V4] + w[V4,V’],更新,V’是V4邻接点),得到Table 4 Table 4 第2次迭代后结果
第2次迭代后,发现通过V4到达V3、V5的距离更近,更新表格 1) 更新距离 d[i] = d[V3] + w[V4,Vi] 2) 更新被更新顶点的最短路径,先将V4的最短路径复制过来,再加上(V4,Vi) V4最短路径是V0->V4,加上(V4,V3)构成V3当前最短路径V0->V4->V3,加上(V4,V5)构成V5当前最短路径V0->V4->V5
i=3 第3次迭代 选出当前距离最小顶点V3,将V3加入S集合,对V3出发的边进行松弛(if d[V] > d[V3] + w[V3,V’],更新,V’是V3邻接点),得到Table 5 Table 5 第3次迭代后的结果
第3次迭代后,发现通过V3到达V5的距离更近,更新表格 1) 更新距离 d[i] = d[V3] + w[V3,Vi] 2) 更新被更新顶点的最短路径,先将V3的最短路径复制过来,再加上(V3,Vi) V3最短路径是V0->V4->V3,加上(V3,V5)构成V5当前最短路径V0->V4->V3->V5
i=4 第4次迭代 选出当前距离最小顶点V5,将V5加入S集合,对V5出发的边进行松弛(if d[V] > d[V5] + w[V5,V’],更新,V’是V5邻接点),得到Table 6 Table 6 第3次迭代后的结果
第4次迭代后,发现没有可以被更新的顶点
i=5 第5次迭代 只剩下一个顶点V1还没有求得最短路径,检查d[V1],发现V0不能到达V1,所以V0到V1没有最短路径 至此,我们就求得了V0到所有点的最短路径及距离。
松弛过程中,对已经得到最短路径的顶点,不再更新距离和最短路径。
五、时间复杂度分析 使用邻接矩阵的Dijkstra算法,时间复杂度在于两点: 1) 每次迭代中,从未得到最短路径的顶点中选择一个距离值最小的顶点 2) 每次得到一个顶点Vi的最短路径后,对Vi出发的边进行松弛
第1点,使用邻接矩阵表示图中,每次迭代都需要遍历一次d[i]来拿到最小值(就是从一个数组中选出一个最小值),这个操作时间复杂度是O(V),总共迭代V次,所以第1点时间复杂度是O(V*V) 第2点,每次迭代中,由于只松弛本次迭代选出的顶点Vi的边,把所有迭代选出的所有顶点松弛的边加起来,这个操作就是松弛所有的边一次,时间复杂度是O(E)
总体计算起来,时间复杂度是O(V*V),V是图中顶点的个数
六、实现
Dijkstra 实现分两种,邻接矩阵和邻接表,细分为:
1) 邻接矩阵
2) 邻接表+二叉堆
3) 邻接表+斐波那契堆
1是邻接矩阵实现,2,3是邻接表实现,这里只实现邻接矩阵的Dijkstra算法。 实现的代码中有很多地方没有验证,功能比较单一,效率有待优化。这里实现的只是有向图,边权值>=0,代码目的只是供初学者参考,有其他需要的读者自行实现。
C/C++ 代码:
#include <iostream>
#include <string>
#include <vector>
#include <utility>
#include <cstdlib>
using namespace std;
const int INFINITE = 100000000;
class DG_AdjMatrix{
public:
//初始化图,手动输入
DG_AdjMatrix()
{
cout << "输入有图顶点个数:";
cin >> m_vexNum;
cout << "输入有向图边数:";
cin >> m_arcNum;
//先开辟空间
m_nodeName = new string[m_vexNum];
m_adjMatrix = new int*[m_vexNum];
for (int i = 0; i < m_vexNum; ++i)
{
m_adjMatrix[i] = new int[m_vexNum];
}
for (int i = 0; i < m_vexNum; ++i)
{
for (int j = 0; j < m_vexNum; ++j)
{
m_adjMatrix[i][j] = INFINITE;
}
}
//初始化顶点和边
cout << endl;
for (int i = 0; i < m_vexNum; ++i)
{
cout << "输入第[" << i+1 << "]个顶点名称: ";
cin >> m_nodeName[i];
}
string x, y;
int weight;
int locateX, locateY;
cout << endl;
for (int i = 0; i < m_arcNum; ++i)
{
cout << "输入第[" << i+1 << "]条边依附的顶点和权值: ";
cin >> x >> y >> weight;
locateX = LocateNode(x);
locateY = LocateNode(y);
m_adjMatrix[locateX][locateY] = weight;
//m_adjMatrix[locateX][locateY] = weight; //无向图
}
}
int LocateNode(string nodeName)
{
for (int i = 0; i < m_vexNum; ++i)
{
if (m_nodeName[i] == nodeName)
{
return i;
}
}
cout << "不存在顶点 " << nodeName << endl;
return -1;
}
//输出邻接矩阵
void PrintAdjMatrix()
{
cout << "vexnum: " << m_vexNum << endl;
cout << "arcnum: " << m_arcNum << endl;
//print adjMatrix
cout << " ";
for (int i = 0; i < m_vexNum; ++i)
{
cout << m_nodeName[i] << " ";
}
cout << endl;
for (int i = 0; i < m_vexNum; ++i)
{
cout << m_nodeName[i];
for (int j = 0; j < m_vexNum; ++j)
{
if (m_adjMatrix[i][j] != INFINITE)
{
cout << " " << m_adjMatrix[i][j];
}
else
{
cout << " " << "-";
}
}
cout << endl;
}
cout << endl;
}
int GetVexNum()
{
return this->m_vexNum;
}
int GetArcNum()
{
return this->m_arcNum;
}
string GetNodeName(int location)
{
if (location >= 0 && location < m_vexNum) //自己做实验可以优化
{
return this->m_nodeName[location];
}
else
{
cout << "不存在这样的顶点" << endl;
return "";
}
}
int** GetAdjMatrix()
{
return this->m_adjMatrix;
}
/************************************************************************/
/* dijkstra求单源点最短路径——邻接矩阵 */
/************************************************************************/
void Dijkstra_AdjMatrix_ShortestPath(DG_AdjMatrix G)
{
string sourceVex;
cout << "输入源点: ";
cin >> sourceVex;
while (LocateNode(sourceVex) == -1)
{
cout << endl;
cout << "输入源点: ";
cin >> sourceVex;
}
int src = LocateNode(sourceVex);
int *d = new int[G.GetVexNum()];
vector<pair<bool, vector<string>>> finished(G.GetVexNum());
for (int i = 0; i < G.GetVexNum(); ++i)
{
d[i] = INFINITE;
finished[i].first = false;
}
int presentNode;
int minDistance;
d[src] = 0;
//剩下V-1次,每次选出一个顶点,确定一条最短路径
for (int i = 0; i < G.GetVexNum(); ++i)
{
minDistance = INFINITE;
for (int w = 0; w < G.GetVexNum(); ++w)
{
if (!finished[w].first && d[w] < minDistance)
{
minDistance = d[w];
presentNode = w;
}
}
finished[presentNode].first = true;
//最后一次如果某点不可达,presentNode仍是上一次循环值
//一个优化:如果只剩下不可达的顶点,通过判断跳出循环
//更新距离
for (int w = 0; w < G.GetVexNum(); ++w)
{
if (!finished[w].first)
{
if (minDistance+G.GetAdjMatrix()[presentNode][w] < d[w])//更新距离
{
d[w] = minDistance + G.GetAdjMatrix()[presentNode][w];
finished[w].second.clear();
finished[w].second.insert(
finished[w].second.begin(),
finished[presentNode].second.begin(),
finished[presentNode].second.end());
finished[w].second.push_back(G.GetNodeName(w));
}
else if(d[w] != INFINITE && finished[w].second.size()==0)//为了产生路径
{
finished[w].second.push_back(G.GetNodeName(w));
}
}
}
}
//输出所有最短路径和距离
for (int i = 0; i < G.GetVexNum(); ++i)
{
cout << GetNodeName(src) << " -> " << GetNodeName(i) << " ";
if (d[i] != INFINITE)
{
cout << " " << d[i] << " ";
cout << GetNodeName(src);
for (vector<string>::iterator ix = finished[i].second.begin();
ix != finished[i].second.end(); ++ix)
{
cout << "->" << *ix;
}
cout << endl;
}
else
{
cout << " - ";
cout << "unreachable";
cout << endl;
}
}
}
private:
int **m_adjMatrix; //邻接矩阵
string *m_nodeName; //结点名称
int m_vexNum; //顶点数
int m_arcNum; //边数
};
int main()
{
DG_AdjMatrix g;
g.PrintAdjMatrix();
g.Dijkstra_AdjMatrix_ShortestPath(g);
system("pause");
return 0;
}
运行截图: