再会迪杰斯特拉(Dijkstra)

迪杰斯特拉算法

算法说明

迪杰斯特拉算法用来求解某一个起点到以其他所有点为终点的最短路径长度;

算法思路-贪心算法

以下图为例

image

  • 指定一个节点(即起点),例如计算“A”到其他节点的最短路径;
  • 引入两个集合(S,U),S集合包含所有已经求出其最短路径的点(以及其最短长度),U集合包括未求出的最短路径的点;
    所有和起点A直接相连的节点更新其与A的距离为路径长度,没有直接相连的设置为+∞;

  • 从U集合中找出距离起点s路径最短的点,加入S集合,例如第一步最小的为A->D,距离为2;
  • 更新U集合路径,if(A->D+D->(B、C、E))(B、C、E),就更新U;

  • 重复执行横线内两步,直到所有的节点都被加到了集合U中;

下面使用迪杰斯特拉算法处理上图

①选取起点为A,首先刷新所有和A点直接通过边相连的点,即B、D点,将A->D置为2,A->B置为4,其他A->C,A->E均为正无穷

算法代码

#include <stdio.h>
#include <iostream>
#include <map>
#include <stack>
#include <vector>
#include <queue>
#include <algorithm>
#include <sstream>
#include <cstring>
#include <string.h>
#include <stdlib.h>
#define N 100005
#define INF 2147483647
typedef long long ll;
using namespace std;
typedef struct Edge {
	ll to;
	ll wei;
	Edge() {
		to=-1;
		wei=INF;//初始都为不可达状态
	}
} E;
ll n,M,s;
vector<E>m[N];	//邻接表
bool wh[N];	//集合U和S,true代表节点已经放入S,false表示还在U集合
ll dis[N];	//存放起点到节点的最短距离 
ll cnt;		//S集合元素个数
void Dijkstra(int s)
{
	wh[s]=true;
	cnt++;
	while (cnt!=n) {
		int Min=INF,Minindex=-1;
		//开始找S集合中距离s最近的节点
		for (int i=1; i<=n; i++) {
			if (!wh[i]&&(dis[i]<Min)) {
				Min=dis[i];
				Minindex=i;
			}
		}
		//此时找到了最小的边
		//将此节点放到S集合
		wh[Minindex]=true;
		cnt++;
		//更新U集合路径
		for (int i=0; i<m[Minindex].size(); i++) {
			dis[m[Minindex][i].to]=min(m[Minindex][i].wei+dis[Minindex],dis[m[Minindex][i].to]);
		}
	}
}
int main()
{
	cin>>n>>M>>s;
	for (int i=1; i<=n; i++) {
		if(i==s) {
			dis[s]=0;
		} else {
			dis[i]=INF;
		}
	}
	ll f,t,w;
	for (ll i=1; i<=M; i++) {	//存储图 
		cin>>f>>t>>w;
		int flag=0;
		//有向图,不双向 
		for (int j=0; j<m[f].size(); j++) {	//重边 
			if (m[f][j].to==t) {
				m[f][j].wei=min(w,m[f][j].wei);
				flag=1;
				break;
			}
		}
		if (flag==0) {
			E e;
			e.to=t;
			e.wei=w;
			m[f].push_back(e);
		}
	}
	//找到和起点直接相连的节点,设置和起点直接相连的距离
	for (int i=0; i<m[s].size(); i++) {
		dis[m[s][i].to]=m[s][i].wei;
	}
	Dijkstra(s);
	for (int i=1; i<=n; i++) {
		if (i==s) {
			(i==1)?cout<<0:cout<<" "<<0;
			continue;
		}
		(i==1)?cout<<dis[i]:cout<<" "<<dis[i];
	}
}

上述算法即是最朴素的迪杰斯特拉算法,需要注意的是,算法考虑了顶点有重边的情况,这是洛谷的题目要求的,题目链接:
P3371 【模板】单源最短路径(弱化版)

但对于没有优化的迪杰斯特拉算法,题目的强化版就会直接TLE,分析朴素的Dijkstra算法,我们发现可以优化的点有:

  • 每次寻找U集合中dis中最小的元素时使用了遍历的方法,但我们可以使用小顶堆保证每次直接弹出最小的值,不需要再去遍历;

对于优先队列,使用stl库的priority_queue实现。

迪杰斯特拉算法的优缺点

  • 优点
    • 算法思路简单,比较容易上手使用;
    • 经典的最短路算法,适合大多数场景;
  • 缺点
    • 时间复杂度和其他算法相比不太理想,适用于节点n不太多的情况;
    • 不能处理存在总花费为负值且从源点S可达的环路的图,因为显然无限兜圈子花费会越来越小;
    • 事实上,存在负环的情况是抓住了贪心算法的弱点导致问题不能解决;
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当然,下面是一个用C++实现的Dijkstra算法的示例: ```cpp #include <iostream> #include <limits.h> #define V 9 int minDistance(int dist[], bool visited[]) { int min = INT_MAX, min_index; for (int v = 0; v < V; v++) { if (!visited[v] && dist[v] <= min) { min = dist[v]; min_index = v; } } return min_index; } void printSolution(int dist[]) { std::cout << "Vertex \t Distance from Source\n"; for (int i = 0; i < V; i++) { std::cout << i << "\t\t" << dist[i] << std::endl; } } void dijkstra(int graph[V][V], int source) { int dist[V]; bool visited[V]; for (int i = 0; i < V; i++) { dist[i] = INT_MAX; visited[i] = false; } dist[source] = 0; for (int count = 0; count < V - 1; count++) { int u = minDistance(dist, visited); visited[u] = true; for (int v = 0; v < V; v++) { if (!visited[v] && graph[u][v] && dist[u] != INT_MAX && dist[u] + graph[u][v] < dist[v]) { dist[v] = dist[u] + graph[u][v]; } } } printSolution(dist); } int main() { int graph[V][V] = {{0, 4, 0, 0, 0, 0, 0, 8, 0}, {4, 0, 8, 0, 0, 0, 0, 11, 0}, {0, 8, 0, 7, 0, 4, 0, 0, 2}, {0, 0, 7, 0, 9, 14, 0, 0, 0}, {0, 0, 0, 9, 0, 10, 0, 0, 0}, {0, 0, 4, 14, 10, 0, 2, 0, 0}, {0, 0, 0, 0, 0, 2, 0, 1, 6}, {8, 11, 0, 0, 0, 0, 1, 0, 7}, {0, 0, 2, 0, 0, 0, 6, 7 ,0}}; dijkstra(graph, 0); return 0; } ``` 这个实现使用Dijkstra算法来计算从指定源点到图中所有其他顶点的最短路径。图以邻接矩阵的形式表示,其中权重用于表示顶点之间的距离。`printSolution`函数用于显示最短路径结果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值