Dijkstra 邻接矩阵 单源点最短路径

来源:互联网 发布:js 私有函数 编辑:程序博客网 时间:2024/06/09 03:54
<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:表示第几次迭代
     Vfinished:表示当前次迭代选出的顶点(就是已经求得最短路径的顶点)
     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 adjMatrixcout << "      ";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;}


     运行截图:
    
0 0