1.最短路径

在一个连通图中,从一个顶点到另一个顶点间可能存在多条路径,而每条路径的边数并不一定相同。如果是一个带权图,那么路径长度为路径上各边的权值的总和。两个顶点间路径长度最短的那条路径称为两个顶点间的最短路径,其路径长度称为最短路径长度

最短路径在实际中有重要的应用价值。如用顶点表示城市,边表示两城市之间的道路,边上的权值表示两城市之间的距离。那么城市A到城市B连通的情况下,哪条路径距离最短呢,这样的问题可以归结为最短路径问题。

求最短路径常见的算法有Dijkstra算法和Floyd算法。本文将详细讲解Dijkstra算法的原理和实现。

2.Dijkstra算法

2.1算法简介

Dijkstra算法是由E.W.Dijkstra于1959年提出,又叫迪科斯彻算法,它应用了贪心算法思想,是目前公认的最好的求解最短路径的方法。算法解决的是有带权连通图(带权有向图也可以)中单个源点到其他顶点的最短路径问题,所以也叫作单源最短路径算法。其主要特点是每次迭代时选择的下一个顶点是标记点之外距离源点最近的顶点。可以求出源点到其他所有点的最短路径,当然也可以指定源点和目标点,求两点之间的最短路径。其做法是迭代至目标点被标记时结束。

2.2算法思想

Dijkstra 算法的基本思路是先将与起点有边直接相连的节点到起点的距离记为对应的边的权重值,将与起点无边直接相连的节点到起点的距离记为无穷大。然后以起点为中心向外层层扩展,计算所有节点到起点的最短距离。每次新扩展到一个距离最短的点后,更新与它有边直接相邻的节点到起点的最短距离。当所有点都扩展进来后,所有节点到起点的最短距离将不会再被改变,因而保证了算法的正确性。

2.3算法基本过程

Dijkstra 算法求解单源最短路径问题的基本步骤如下:
(1)设立U 和Y两个节点集合, Y用于保存所有未被访问的节点,U 记录所有已经访问过的节点。已经被访问指的是节点已经被纳入最短路径中。
(2)从Y中找出距离起点最近的节点,放入U中,并更新与这个节点有边直接相连的相邻节点到起始节点的最短距离。
(3)重复步骤(2)直到Y集合为空,即从起点出发可以到达的所有顶点都在集合U中为止。

Dijkstra 算法的基本思想和求解步骤决定了Dijkstra算法只能解决最基本的在起点和终点之间求最短路径的问题,无法解决添加了其他限制条件的,如要求经过指定中间节点集的最短路径问题,Dijkstra 算法是无法直接解决的。

2.4算法实例过程描述

已经带权有向图G如下图所示,现求节点2到节点3的最短路径。这里要求节点ID从0开始并且连续编号,且边的权值大于0。后面的代码实现也是要遵循这两个前提条件的。

如果两个节点见未直接相连,则节点间的距离设为无穷大,可用一个很大的数表示。

图中红色数字表示边的ID,圆圈中的数字为节点ID,边旁边的黑色数字表示节点间边的权值。并且所有ID均不重复。

(1)初始时,标记起点2为已访问的节点,并置于集合U中,此时集合U={2},集合Y={0,1,3}。

且初始化其它节点到起点2的距离distance[N]数组,N表示图中节点数。distance[N]数组不仅需要保存其它节点到起点2的距离,也要保存起点2到达该节点的最短路径的最后一个中间节点,这里称为当前节点的前节点。如果没有前一个节点则设为-1。

此时,distance[N]数组初始化为 {distance[0]={∞,-1}, distance[1]={2,2}, distance[2]={0,-1}, distance[3]={10,2}}。标粗的表示该节点已经置于集合U中。

(2)在集合Y中找出距离起点2最短的节点,遍历数组distance[N]得节点1距离起点2最近,并将其加入集合U中。此时集合U={2,1},集合Y={0,3}。

(3)以新加入集合U的节点1为中间节点,更新起点2到其它节点之间的最短距离。

对节点0,因为节点2到节点0的距离为无穷大∞,大于起点2通过节点1到节点0的距离2+3=5,并且节点0的前屈节点变为1,所以更新distance[0]={5,1};

对节点3,同理,因为节点2到节点3的距离为10,小于节点2通过节点1到节点3的距离1+∞=∞,所以无需更新。

所以,更新完之后的distance[N]取值情况为:{distance[0]={5,1}, distance[1]={2,2}, distance[2]={0,-1}, distance[3]={10,2}}。

(4)重复步骤2,继续在集合Y中寻找距离起点2最短的节点,并访问它。遍历数组distance[N]知道节点0到起点2的距离最短为5,其节点0加入集合U中。此时集合U={2,1,0},集合Y={3}。

(5)重复步骤3,以节点0为中间节点更新集合Y中节点到起点2的距离。因为distance[0].first+matrix[0][3]=5+4=9,小于distance[3].first=10,所以更新distance[3]={9,0}。

此时distance[N]取值情况为:{distance[0]={5,1}distance[1]={2,2}, distance[2]={0,-1}, distance[3]={9,0}}。

(6)重复步骤2,再集合Y中找出距离起点2最近的节点,遍历distance[N]可知节点3距离最近,并将其纳入集合U中。此时集合U={2,1,0,3},集合Y={}。

(7)重复步骤3,以节点3为中间节点更新集合Y中节点到起点2的距离,此时发现集合Y为空,过程结束。

最后我们获得了加入集合U的所有节点,因为没有节点都记录了自己的前驱节点,所以可以获得从起点到任意目的节点见的最短路径。

3.Dijkstra算法具体实现

以上面的描述为基础,编码实现Dijkstra算法。

3.1输入

(1)图的信息
文件Graph(以逗号为分隔符的文本文件)给出图的数据,文件每行以换行符(ASCII’\n’即0x0a)为结尾。

图的数据中,每一行包含如下的信息:
EdgeID,SourceID,DestinationID,Cost

其中,EdgeID为该有向边的索引,SourceID为该有向边的起始顶点的索引,DestinationID为该有向边的终止顶点的索引,Cost为该有向边的权重。顶点与有向边的索引均从0开始编号,这里要求连续,且保证索引不重复。

(2)起点与终点
程序运行过程中,输入起点和终点。

3.2输出

打印输出起点至终点间最短路径顺序经过的节点,并且输出最短路径的长度,即边的权值和。

3.3相关数据结构

(1)图的存储结构
图采用邻接矩阵存储,由图的信息构造。

(2)集合U和Y
没有实际存储,逻辑的在图邻接矩阵对角线的bool值来表示在集合U还是在集合Y。比如邻接矩阵matrix[2][2]初始时为0,即自己到自己的距离是0。当被纳入集合U中,将其matrix[2][2]设置为1即可。

(3)distance[N]
distance[N]记录了未被纳入最短路径的集合Y中的节点距离起点的最短距离,以及它的前驱节点。因为是二元信息组,所以采用C++的STL标准模板库中的键值对容器pair。pair< int,int>第一个元素表示节点ID,第二元素表示该节点的前驱节点。

(4)起点到其它所有节点的最短路径
采用map< int,int>容器存储。如果再给定任意非起点的节点作为终点,即可从起点到其它所有节点的最短路径找出起点到终点的最短路径,并且根据关系矩阵求出最短路径的长度。

3.4时间复杂度

算法中构造邻接矩阵的时间复杂度是O(n2)O(n^2),求最短路径部分又两层循环构成,外循环n-1次,内循环为n次,所以时间复杂度为O(n2)O(n^2),因此总的时间复杂度为O(n2)O(n^2)。这里的n表示图的节点数。

3.5具体实现

Dijkstra算法核心代码:

/**************************************************
func:求带权有向图的单源最短路径;
para:matrix:图的邻接矩阵;nodeNum:图的节点数;startID:起始节点;shortestPath:最短路径;
retu:0:成功;-1:失败,表明从起点出发有不可到达的节点
**************************************************/
int djkstra(int* (*matrix),int nodeNum,int startID,map<int,int>& shortestPath){if(matrix==NULL||nodeNum<1||startID<0||startID>nodeNum-1)return -1;shortestPath.insert(make_pair(startID,-1));//startID为路径起点,进入容器matrix[startID][startID]=1;//表示顶点startID在集合U中pair<int,int>* distance=new pair<int,int>[nodeNum];//顶点startID到其它定点距离,后一个int表示当前节点的前一个节点memset(distance,0,nodeNum*sizeof(pair<int,int>));//初始化startID到集合Y中顶点的距离值for(int i=0;i<nodeNum;++i){if(i==startID) continue;distance[i]=make_pair(matrix[startID][i],-1);if(matrix[startID][i]!=INFINITY)distance[i].second=startID;}//循环的次数是Y集合中的定点数,去除起点,只有nodeNum-1个for(int i=0;i<nodeNum-1;++i){int minID=0,minWeight=INFINITY;for(int j=0;j<nodeNum;++j){if(j==startID) continue;if(matrix[j][j]!=1&&distance[j].first<minWeight){minWeight=distance[j].first;minID=j;}}if(minWeight==INFINITY) return -1; //从startID=0到集合V-U中没有路径shortestPath.insert(make_pair(minID,distance[minID].second));matrix[minID][minID]=1;  //设置为已访问//更新顶点startID通过顶点minID到集合V-U中的定点的最短距离for(int j=0;j<nodeNum;++j){if(matrix[j][j]!=1){if(distance[j].first>distance[minID].first+matrix[minID][j]){distance[j].first=distance[minID].first+matrix[minID][j];distance[j].second=minID;}}}}delete[] distance;return 0;
}

求给定终点的最短路径:

/**************************************************
func:求给定终点的最短路径以及路径长度
para:matrix:图的邻接矩阵;startID:起点;endID:终点;shortestTwoNode:两点间的最短路径;shortestPath:起点到其它所有节点的最短路径;minWeight:两点间最短路径长度
retu:成功返回0,失败返回-1
**************************************************/
int getShortestPath(int* (*matrix),int startID,int endID,list<int>& shortestTwoNode,map<int,int>& shortestPath,int& minWeight){if(startID==endID||startID<0||endID<0)return -1;std::stack<int> nodeS; map<int,int>::iterator it=shortestPath.find(endID);while(it!=shortestPath.end()){nodeS.push((*it).first);if(it->first==startID)break;it=shortestPath.find(it->second);}while(!nodeS.empty()){int topEle=nodeS.top();nodeS.pop();shortestTwoNode.push_back(topEle);if(!nodeS.empty())minWeight+=matrix[topEle][nodeS.top()];}return 0;
}

相关辅助函数:

//qsort函数需要的比较函数,按照升序排序
int comp(const void*a,const void*b)
{return *(int*)a-*(int*)b;
}//按指定分隔符分割字符串
//src:源字符串 delimiter:分隔符集合
vector<string> split(const string& src,const string& delimiter)
{vector<string> strRes;if(src=="")return strRes;int maxSubstrNum=src.size();int* pos=new int[maxSubstrNum];memset(pos,NULL,maxSubstrNum*sizeof(int));int j=0;for(int i=0;i<delimiter.size();++i){string::size_type index=src.find(delimiter[i]);while(index!=string::npos){pos[j++]=index;index=src.find(delimiter[i],index+1);}       }//排序qsort(pos,j,sizeof(int),comp);//取出第一个子串string substrFir=src.substr(0,pos[0]);if(substrFir!="")strRes.push_back(substrFir);//取出中间j-1个子串for(int i=0;i<j-1;++i){string substr=src.substr(pos[i]+1,pos[i+1]-pos[i]-1);if(substr!="")strRes.push_back(substr);}//取出最后一个子串string substrLast=src.substr(pos[j-1]+1,src.size()-pos[j-1]-1);if(substrLast!="")strRes.push_back(substrLast);   delete[] pos;return strRes;
}//根据输入文件读取图的边信息
int getEdge(char* filePath,vector<Edge>& edgeVec){if(filePath==NULL||*filePath=='\0')return -1;char buffer[32]="";Edge edgeTemp;memset(&edgeTemp,0,sizeof(Edge));ifstream graph(filePath);if(graph.is_open()){while(graph.peek()!=EOF){graph.getline(buffer,32,'\n');vector<string> edgeInfo=split(buffer,",");if(edgeInfo.size()!=4)return -1;edgeTemp.linkID=atoi(edgeInfo[0].c_str());edgeTemp.start=atoi(edgeInfo[1].c_str());edgeTemp.end=atoi(edgeInfo[2].c_str());edgeTemp.cost=atoi(edgeInfo[3].c_str());edgeVec.push_back(edgeTemp);}graph.close();return 0;}else return -1;
}//根据图的边信息构造邻接矩阵
int getMatrix(vector<Edge>& edgeVec,int* (*&matrix),int& nodeNum){set<int> nodeSet;for(int i=0;i<edgeVec.size();++i){nodeSet.insert(edgeVec[i].start);nodeSet.insert(edgeVec[i].end);}nodeNum=nodeSet.size();matrix=new int*[nodeNum];memset(matrix,0,nodeNum*sizeof(int*));for(int i=0;i<nodeNum;++i){matrix[i]=new int[nodeNum];memset(matrix[i],0,sizeof(int)*nodeNum);}//为关系矩阵赋值for(int row=0;row<nodeNum;++row){for(int colum=0;colum<nodeNum;++colum){if(row==colum)matrix[row][colum]=0;else{   vector<Edge>::iterator it=find(edgeVec.begin(),edgeVec.end(),Edge(0,row,colum,0));if(it!=edgeVec.end()){matrix[row][colum]=it->cost;}else{matrix[row][colum]=INFINITY;}}}}return 0;
}

main函数

#include <iostream>
#include <vector>
#include <map>
#include <set>
#include <list>
#include <algorithm>
#include <fstream>
#include <string>
#include <stack>
using namespace std;#define INFINITY 0x0fffffffstruct Edge{int linkID;int start;int end;int cost;Edge(){this->linkID=0;this->start=0;this->end=0;this->cost=0;}Edge(int linkID,int start,int end,int cost){this->linkID=linkID;this->start=start;this->end=end;this->cost=cost;}bool operator==(const Edge& edge) const{return edge.start==start && edge.end == end;//这里可以自定匹配个数}
};vector<Edge> edgeVec; //带权有向图边的集合int main(int argc,char* argv[]){if(argc!=2)return -1;vector<Edge> edgeVec;getEdge(argv[1],edgeVec);int* (*matrix)=NULL;int nodeNum=0;getMatrix(edgeVec,matrix,nodeNum);//构造关系矩阵map<int,int> shortestPath;  //起点到所有其他节点的最短路径list<int> shortestTwoNode;  //起点到终点的最短路径int shortestTwoNodeWeight=0; int startID=0,endID=0;cout<<"input startID endID"<<endl;cin>>startID>>endID;int res=djkstra(matrix,nodeNum,startID,shortestPath);if(res==-1)cout<<"get shortest path start from "<<startID<<" failed "<<endl;elsegetShortestPath(matrix,startID,endID,shortestTwoNode,shortestPath,shortestTwoNodeWeight);cout<<startID<<" to "<<endID<<":";for(list<int>::iterator it=shortestTwoNode.begin();it!=shortestTwoNode.end();++it){cout<<*it<<" ";}cout<<endl;cout<<"weight:"<<shortestTwoNodeWeight<<endl;getchar();
}

3.6实验结果

以上面描述的图为例,输入图的信息文本文件如下:

0,0,1,2
1,1,0,3
2,0,2,5
3,2,1,2
4,2,3,10
5,3,2,8
6,3,1,5
7,0,3,4

求节点2到节点3的最短路径,输出结果如下:

再求节点0到2的最短路径,输出结果如下:

4.小结

(1)本文实现的Djkstra求单源最短路径,在具体实现上采用邻接矩阵存储图的信息,所以要求节点索引均从0开始编号且连续。这一点需要改进,可采取邻接表存储图的信息。

(2)本文将图的信息单独存储在文本文件中,将程序的输入与代码分离,降低了耦合性,提升了程序的扩展性。

(3)本文的做法是将起点到其它所有节点的最短路径求出后再求给定的终点与起点之间的最短路径,其实可以不必如此。具体做法是在访问到给定的终点时,停止求起点到其它节点的最短路径,可提高算法性能。

(4)书写过程难免误笔,请网友留言指正。


参考文献

[1]经过指定的中间节点集的最短路径算法[J].黄书力,胡大裟,蒋玉明.计算机工程与应用:2015,51(11).
[2]算法与数据结构.C语言第二版.张乃孝.高等教育出版社.
[3]http://blog.csdn.net/longshengguoji/article/details/10756003.

Dijkstra算法求单源最短路径相关推荐

  1. Dijkstra(迪杰斯特拉)算法求单源最短路径问题

    Dijkstra(迪杰斯特拉)算法求单源最短路径问题 重要的事情说三遍:代码不是我写的!代码不是我写的!代码不是我写的! 第一个算法是严蔚敏数据结构(C语言版)上写的,第二个算法是王道数据结构上写的, ...

  2. 51nod 1445 变色DNA ( Bellman-Ford算法求单源最短路径)

    1445 变色DNA 基准时间限制:1 秒 空间限制:131072 KB 分值: 40 难度:4级算法题 有一只特别的狼,它在每个夜晚会进行变色,研究发现它可以变成N种颜色之一,将这些颜色标号为0,1 ...

  3. 【2023王道数据结构】【图】通过C++实现图的BFS(广度优先遍历)算法求单源最短路径问题C、C++完整实现(可直接运行)

    ~~~笔锋至此又怎能平淡而终,故事开始便不承认普通✌✌✌ ✌ 题目及题解持续更新中 [2023王道数据结构目录]课后算法设计题C.C++代码实现完整版大全 题目: 通过C++实现图的BFS(广度优先遍 ...

  4. dijkstra算法PHP,单源最短路径(dijkstra算法)php实现

    做一个医学项目,其中在病例评分时会用到单源最短路径的算法.单源最短路径的dijkstra算法的思路如下: 如果存在一条从i到j的最短路径(Vi.....Vk,Vj),Vk是Vj前面的一顶点.那么(Vi ...

  5. 【算法】【ACM】深入理解Dijkstra算法(单源最短路径算法)

    Dijkstra算法是用来求解从某个源点到其他各顶点的最短路径(单源最短路径). 下面的Dijkstra算法的讲解都是基于这个有向图,在遇到其他问题可以类比. 算法的基本思想: 把图中的定点分成两组, ...

  6. java 有向图 最短路径算法_java使用Dijkstra算法实现单源最短路径

    单源最短路径问题,即在图中求出给定顶点到其它任一顶点的最短路径.在弄清楚如何求算单源最短路径问题之前,必须弄清楚最短路径的最优子结构性质. 一.最短路径的最优子结构性质 该性质描述为:如果P(i,j) ...

  7. Dijkstra算法求解单源最短路径问题

    文章目录 一 前言 二 Dijkstra 算法讲解 1. 贪心算法的证明 2. 算法实现说明 3. 初版Dijkstra算法代码 三 时间复杂度优化 1. 优化策略 2. 优化后的代码 四 结语 一 ...

  8. Dijkstra算法(单源最短路径)

    对于有向无环图DAG,我们可以利用先拓扑排序得到顶点的一个序列,然后按照此序列进行最短路径的推进(更新当前顶点的邻接顶点的dist值),如 图的邻接表数组实现及其应用 对于有环图,通常使用的是Dijk ...

  9. 【算法】Dijkstra算法(单源最短路径问题) 邻接矩阵和邻接表实现

    Dijkstra算法可使用的前提:不存在负圈. 负圈:负圈又称负环,就是说一个全部由负权的边组成的环,这样的话不存在最短路,因为每在环中转一圈路径总长就会边小. 算法描述: 1.找到最短距离已确定的顶 ...

最新文章

  1. opengl es 2.0环境
  2. Java设计模式——装饰者模式
  3. HTML5学习笔记(一):HTML简介
  4. centos 安装rar 和 unrar
  5. Winxp中加密自己的用户目录
  6. java integer常量池_为什么Integer常量池的行为在127发生变化?
  7. MyCat双机HA高可用集群搭建_HAProxy安装和配置---MyCat分布式数据库集群架构工作笔记0028
  8. Sip 响应状态码功能对照详解
  9. 二分图匹配问题之km算法代码
  10. 知道华为HMS ML Kit文本识别、银行卡识别、通用卡证识别、身份证识别的区别吗?深度好文教你区分
  11. 分享一篇酷炫粒子风暴代码!
  12. excel单元格内容拆分_Excel | 单元格内容换行方法
  13. SkyLine——3DGIS三维地理信息系统软件产品介绍
  14. Linux移植EC20 4G模块驱动简易教程
  15. 国产某偶像剧天才程序员爱心C语言作业用C++写的代码,结果是Py脚本文件
  16. 【Python处理EXCEL】--pandas导入Excel文件
  17. “无实物尝百味”通过控制微电流刺激产生味觉—1.硬件设计篇
  18. Force 10交换机初始配置文档
  19. swiper实现icons列表超出数量滑动轮播
  20. 十大3D立体游戏强烈推荐

热门文章

  1. TCP/IP研究(2)-TCB
  2. EJB3.0高速入门项目开发步骤
  3. ubuntu安装最新的rails-4.2.0
  4. System.getProperty的用法
  5. android开发我的新浪微博客户端系列教程
  6. python对XML 操作
  7. RTL8188ce无线网卡驱动在Ubuntu/Ubuntu Kylin 13.10中的安装-转
  8. linux图形界面setup,linux setup命令参数及用法详解--linux图形界面设置命令
  9. 1091. Acute Stroke (30)-PAT甲级真题(广度优先搜索)
  10. 1044. 火星数字(20)-PAT乙级真题