基于优先队列的Dijkstra算法

本文详细介绍了Dijkstra算法解决单源最短路径问题的基本原理,重点讲解了如何通过优先队列实现时间复杂度优化,并提供了C++代码实例。讨论了算法适用场景及负权边处理的局限性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

最短路径问题,即在给定的连接图中,求解节点之间的最短路径。Dijkstra算法是典型的单源最短路径算法,单源即只能求解某个节点到其他节点的最短路径。另外,此算法不能处理边权重为负的情况。

一、最短路径问题

最短路径问题是图论的一个经典算法问题,最短路径问题包含多种问题形式,本文着重讲解单源最短路径问题及其优化求解方法。如下图所示,给出由多个节点相互连接构成的图,节点之间连接的好坏用权重来衡量,我们要求解某点到任意点的最短路径,这是典型的单源最短路径问题,我们可以使用Dijkstra算法对其进行求解。
最短路径问题

二、Dijkstra算法

Dijkstra算法是典型的单源最短路径算法,其基本思想是以起点为中心,层层向外扩展,直到覆盖所有的节点,即找到了所有节点的最短路径。通常来说,我们需要准备两个集合S和U,S为已经找到最短路径的点的集合,U为尚未找到最短路径的点的集合.初始时源点,即出发点置于S中,源点的最短路径值始终为0,其余点则置于U中。从S中的点出发,遍历U中所有的点,每次找到一个点的最短路径,即从U中选择一个点加入S,直到U为空集。很明显,每次我们都需要从S中点出发,去查看U中的每个点并找到一个最小值,很明显算法的时间复杂度为O(n^2)。

三、优先队列

优先队列是队列的一种特殊形式,它满足队列的所有条件,区别于队列的是优先队列中的元素按照某种特定顺序排列,利用优先队列的这种特性,我们可以实现时间复杂度更低的Dijkstra算法。算法基本思想如下:
(1)初始化节点,赋予每个节点路径值为一个极大值,源点的路径值为0
(2)定义一个优先队列,队列中元素记录了节点的编号和节点的最短路径值,将源点压入队列。
(3)当队列非空,执行以下操作:
(a)u等于队顶的节点,w等于队顶节点的最短路径值
(b)遍历u的所有边,如果能找到节点v最短路径值小于v的当前值,更新v,将v压入队列。
(4)结束
使用优先队列求解的时间复杂度为O(nlogn)。

四、C++代码

使用C++实现上述算法,代码如下,编译环境为VS2019,最后得输出的结果是源点到各个节点的最短路径值。

#include<iostream>
#include<vector>
#include<queue>

using namespace std;

//节点信息类(包含节点和最短路径值)
class info
{
public:
	int node;//节点
	double weight;//权重
public:
	info(int node,double weight){
		this->node=node;
		this->weight=weight;
	}
	friend bool operator<(const info &info1 , const info &info2)
	{
		return info1.weight>info2.weight;  
	}
};

//图类
class cgraph{
public:
	int V;//顶点数量
	vector<info>*adj;//邻接矩阵(记录每个节点的所有边)
	double *dis;//最优距离
public:
	cgraph(int V);//构造函数(V为节点数)
	void add_edge(int nd1,int nd2,double weight);//添加边(节点1,节点2,边权重)
	void get_shortest_path(int src);//求解最短路径
};

cgraph::cgraph(int V)
{
		this->V=V;
		adj=new vector<info>[V];
		dis=new double[V];
		for(int i=0;i<V;i++)dis[i]=1e8;
	}

void cgraph::add_edge(int nd1,int nd2,double weight)
{
		adj[nd1].push_back(info(nd2,weight));
		adj[nd2].push_back(info(nd1,weight));
}

void cgraph::get_shortest_path(int src)
{
	priority_queue<info>current_node;//优先队列,记录节点编号和最短路径值,最短路径值小的优先
	dis[src]=0;//源点的最短路径值为0
	current_node.push(info(src,0));//源点压入队列

	//当队列非空继续寻找
	while(!current_node.empty()){
		int u=current_node.top().node;//u等于队顶节点
		double w=current_node.top().weight;//w等于对顶节点最短路径值
		current_node.pop();//弹出队顶节点
		//从对顶节点出发,更新节点的最短路径值
		for(int i=0;i<(int)adj[u].size();i++){
			int v=adj[u][i].node;
			if(w+adj[u][i].weight<dis[v]){
				dis[v]=w+adj[u][i].weight;
				current_node.push(info(v,dis[v]));
			}
		}
	}
	//输出dis追踪记录的最短路径值
	for(int i=0;i<V;i++)cout<<src<<"->"<<i<<"      dis="<<dis[i]<<endl;
}

int main()
{
	cgraph graph(9);//构造图

	//添加边
	graph.add_edge(0,1,4);
	graph.add_edge(0,7,8);
	graph.add_edge(1,2,8);
	graph.add_edge(1,7,11);
	graph.add_edge(2,3,7);
	graph.add_edge(2,5,4);
	graph.add_edge(2,8,2);
	graph.add_edge(3,4,9);
	graph.add_edge(3,5,14);
	graph.add_edge(4,5,10);
	graph.add_edge(5,6,2);
	graph.add_edge(6,7,1);
	graph.add_edge(6,8,6);
	graph.add_edge(7,8,7);

	//求解
	graph.get_shortest_path(0);

	cin.get();
}
### 基于优先队列Dijkstra算法实现与应用 #### 1. 普通 Dijkstra 算法的缺陷 普通的 Dijkstra 算法通过遍历所有未访问节点来寻找当前距离最小的节点,这一过程的时间复杂度为 \(O(n)\),其中 \(n\) 是节点的数量。当图较大时,这种操作会显著增加计算开销[^1]。 为了提高效率,可以引入 **优先队列** 来替代线性扫描的方式选择下一个要处理的节点。优先队列能够快速获取具有最小权值的节点,从而降低每次查找最小距离的操作成本。 --- #### 2. 使用优先队列优化的核心思路 在基于优先队列Dijkstra 算法中,核心改进在于利用堆结构(通常是最小堆)维护待处理节点的距离信息。具体来说: - 将起始节点加入优先队列,并初始化其距离为 0; - 不断从优先队列中取出距离最小的节点,更新与其相邻节点的距离; - 如果某个节点已经被处理过,则跳过该节点继续处理其他节点。 这种方法使得每轮迭代都能高效地选取全局最优候选节点,而无需逐一比较整个列表中的元素[^3]。 --- #### 3. 时间复杂度分析 对于含有 \(V\) 个顶点和 \(E\) 条边的稀疏图而言: - 若采用邻接表表示并配合二叉堆作为底层数据结构,则总时间复杂度降为 \(O((V+E) \log V)\)[^4]。 - 进一步如果选用斐波那契堆代替传统二叉堆的话还可以进一步减少摊还意义上的运行耗时至接近线性的水平即大约等于\( O(V\lg{}V + E) \). 需要注意的是,在实际编码过程中由于语言特性和库函数支持程度不同等原因可能导致理论上的优势无法完全体现出来因此需视具体情况灵活调整策略. --- #### 4. Python 实现代码示例 以下是使用 `heapq` 库构建最小堆完成上述逻辑的具体例子: ```python import heapq def dijkstra(graph, start): n = len(graph) dist = [float('inf')] * n # 初始化距离数组,默认无穷大 visited = [False] * n # 记录是否已确定最短路径 heap = [(0, start)] # (distance, node) dist[start] = 0 # 起点到自身的距离设为零 while heap: d_u, u = heapq.heappop(heap) # 取出当前最近的节点 if visited[u]: continue # 已经确认过的节点不再重复考虑 visited[u] = True # 标记此节点已被访问 for v, weight in graph[u]: # 遍历邻居节点 new_dist = d_u + weight if not visited[v] and new_dist < dist[v]: dist[v] = new_dist # 更新更优解 heapq.heappush(heap, (new_dist, v)) # 加入新的候选项 return dist # 返回最终结果向量 # 测试用例:无向加权图(邻接表形式) if __name__ == "__main__": G = [ [(1, 7), (2, 9), (5, 14)], # Node 0 [(0, 7), (2, 10), (3, 15)], # Node 1 [(0, 9), (1, 10), (3, 11), (5, 2)], # Node 2 [(1, 15), (2, 11), (4, 6)], # Node 3 [(3, 6), (5, 9)], # Node 4 [(0, 14), (2, 2), (4, 9)] # Node 5 ] result = dijkstra(G, 0) print(result) # 输出各点到起点的最短距离 ``` 以上程序展示了如何借助内置模块轻松实现高效的单源最短路查询功能[^2]. --- #### 5. 注意事项 - 当存在负权重边时,本方法失效;此时应改用 Bellman-Ford 或 SPFA 等更适合此类情况的技术手段解决相应难题。 - 对于大规模网络环境下的动态规划场景下可能还需要额外考量内存占用平衡等因素影响整体性能表现。 ---
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值