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

本文详细介绍了Dijkstra算法用于求解单源点最短路径的问题,适用于边权值非负的图。算法通过松弛操作逐步找到源点到所有顶点的最短路径,并给出了具体步骤和邻接矩阵的实现方式。
<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 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;
}


     运行截图:
    
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值